17

I am trying to implement marquee effect to the layout to help it scroll/animate from right inner side to left inner side just as the ticker view.

From the below two links, you would be able to get more knowledge just as stock markets keep users updated by showing the values in a circular constant way.

1) http://www.sify.com/finance/livemarkets/

2) http://terminal.moneycontrol.com/index.php?wl=indices

For this i have implemented below code to make somewhat similar.

public class HorizonalSlideActivity extends Activity
{
    private LinearLayout horizontalOuterLayout;
    private HorizontalScrollView horizontalScrollview;
    private int scrollMax;
    private int scrollPos = 0;
    private TimerTask scrollerSchedule;
    private Timer scrollTimer = null;
    private ScrollAdapter adapter = null;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.horizontal_layout);

        adapter = new ScrollAdapter(HorizonalSlideActivity.this);

        horizontalScrollview = (HorizontalScrollView) findViewById(R.id.horiztonal_scrollview_id);
        horizontalScrollview.setOnTouchListener(new OnTouchListener()
        {
            @Override
            public boolean onTouch(View v, MotionEvent event)
            {
                return false;
            }
        });
        horizontalOuterLayout = (LinearLayout) findViewById(R.id.horiztonal_outer_layout_id);
        horizontalTextView = (TextView) findViewById(R.id.horizontal_textview_id);
        horizontalScrollview.setHorizontalScrollBarEnabled(false);

        addData();


        ViewTreeObserver vto = horizontalOuterLayout.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener()
        {
            @Override
            public void onGlobalLayout()
            {
                horizontalOuterLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                getScrollMaxAmount();
                startAutoScrolling();
            }
        }); 
    }

    private void addData()
    {
        for (int i = 0; i < adapter.getCount(); i++)
        {
            View convertView = adapter.getView(i, null, null);
            horizontalOuterLayout.addView(convertView);
        }
    }

    public void getScrollMaxAmount()
    {
        Display display = getWindowManager().getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        int width = size.x;
        Log.e("getScrollMaxAmount", "getWidth == "+width);
        Log.e("getScrollMaxAmount", "getMeasuredWidth == "+horizontalOuterLayout.getMeasuredWidth());
        int actualWidth = (horizontalOuterLayout.getMeasuredWidth() - width);
        Log.e("getScrollMaxAmount", "actualWidth == "+actualWidth);
        scrollMax = actualWidth;
        Log.e("getScrollMaxAmount", "scrollMax == "+scrollMax);
    }

    public void startAutoScrolling()
    {
        if (scrollTimer == null)
        {
            scrollTimer = new Timer();
            final Runnable TimerTick = new Runnable()
            {
                public void run()
                {
                    moveScrollView();
                }
            };

            if (scrollerSchedule != null)
            {
                scrollerSchedule.cancel();
                scrollerSchedule = null;
            }
            scrollerSchedule = new TimerTask()
            {
                @Override
                public void run()
                {
                    runOnUiThread(TimerTick);
                }
            };

            scrollTimer.schedule(scrollerSchedule, 3000, 30);
        }
    }

    public void moveScrollView()
    {
        Log.e("moveScrollView", "scrollMax == "+scrollMax);
        scrollPos = (int) (horizontalScrollview.getScrollX() + 1.0);
        Log.e("moveScrollView", "scrollPos == "+scrollPos);
        if (scrollPos >= scrollMax)
        {
            scrollPos = 0;
        }
        horizontalScrollview.scrollTo(scrollPos, 0);
    }

    public void stopAutoScrolling()
    {
        if (scrollTimer != null)
        {
            scrollTimer.cancel();
            scrollTimer = null;
        }
    }

    public void onBackPressed()
    {
        super.onBackPressed();
        finish();
    }

    public void onPause()
    {
        super.onPause();
        finish();
    }

    public void onDestroy()
    {
        clearTimerTaks(scrollerSchedule);
        clearTimers(scrollTimer);

        scrollerSchedule = null;
        scrollTimer = null;
        super.onDestroy();
    }

    private void clearTimers(Timer timer)
    {
        if (timer != null)
        {
            timer.cancel();
            timer = null;
        }
    }

    private void clearTimerTaks(TimerTask timerTask)
    {
        if (timerTask != null)
        {
            timerTask.cancel();
            timerTask = null;
        }
    }
}

Here is the layout for this file:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <HorizontalScrollView
        android:id="@+id/horiztonal_scrollview_id"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fadingEdge="none" >

        <LinearLayout
            android:id="@+id/horiztonal_outer_layout_id"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="#ffffff"
            android:gravity="center_vertical"
            android:orientation="horizontal"
            android:paddingBottom="5dip"
            android:paddingTop="5dip" >
        </LinearLayout>
    </HorizontalScrollView>

</LinearLayout>

Adapter class to show data:

public class ScrollAdapter extends BaseAdapter
{

    private Context context;

    public ScrollAdapter(Context context)
    {
        this.context = context;
    }

    @Override
    public int getCount()
    {
        return 10;
    }

    @Override
    public Object getItem(int position)
    {
        return position;
    }

    @Override
    public long getItemId(int position)
    {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent)
    {
        LayoutInflater inflater = ((Activity)context).getLayoutInflater();
        convertView = inflater.inflate(R.layout.scroll_child, null);
        return convertView;
    }

}

And its xml file:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/txtPercent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:paddingLeft="30dp"
        android:paddingRight="30dp"
        android:text="0.14%" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="@android:color/white" />

    <TextView
        android:id="@+id/txtData"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:paddingLeft="30dp"
        android:paddingRight="30dp"
        android:text="data" />

</LinearLayout>

By implementing this, i somewhat reached nearer but could not make what is desired. Please help me to make it circular without any interruption and restart scrolling after the last data moves inside from left side.

Here is the desired result:

enter image description here

Also i have checked this link, it gives the scroll effect what is needed but does not shows the whole data, its shows only 3 to 4 views but as you can see i have added 10 data in adapter class.

Any sort of help would be appreciable.

Thanks in advance.

Jignesh Jain
  • 1,438
  • 12
  • 26
  • why don't you user horizontal recyclerview with self scrolling effect as provided by jovin, it will be more handy...rather than making your own marquee view. – Reprator Jul 30 '15 at 03:18
  • I implemented something like this in a personal project recently. Here is a short video of it running: https://www.youtube.com/watch?v=iItBvCck4jo and here is the repo: https://github.com/octohub/ticker. I hope it is of some help. – Dick Lucas Jul 30 '15 at 16:18

2 Answers2

12

It can be accomplished using Recycleview and a autoscroll runnable . adding the code snippet here

1. MainActivity (MarqueeViewSample.java)

package com.test.mo.test;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;

/**
* Created by jovin.pj on 29-07-2015.
*/
public class MarqueeViewSample extends Activity {

private final Runnable SCROLLING_RUNNABLE = new Runnable() {

    @Override
    public void run() {
        final int duration = 10;
        final int pixelsToMove = 10;
        marqueList.smoothScrollBy(pixelsToMove, 0);
        mHandler.postDelayed(this, duration);
    }
};

private final Handler mHandler = new Handler(Looper.getMainLooper());
private RecyclerView marqueList;
//private boolean loading = true;
private boolean foundTotalPixel = true;
private int pastVisiblesItems, visibleItemCount, totalItemCount;
private int totalMovedPixel;
private int totalPixel;

/**
 * Called when the activity is first created.
 */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    marqueList = (RecyclerView) findViewById(R.id.marqueList);
    final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
    layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
    marqueList.setLayoutManager(layoutManager);
    marqueList.setAdapter(new ScrollAdapter());

    marqueList.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            totalMovedPixel = totalMovedPixel + dx;
            visibleItemCount = layoutManager.getChildCount();
            totalItemCount = layoutManager.getItemCount();
            pastVisiblesItems = layoutManager.findFirstVisibleItemPosition();
            if (foundTotalPixel) {
                if (totalItemCount > 2) {
                    View headerView = layoutManager.getChildAt(0);
                    View itemView = layoutManager.getChildAt(1);

                    if (itemView != null && headerView != null) {
                        /*total visible scrolling part is total pixel's of total item's count and header view*/
                        totalPixel = /*-c.getTop() +*/ ((totalItemCount - 2) * itemView.getWidth()) + (1 * headerView.getWidth());
                        Log.v("...", "Total pixel x!" + totalPixel);
                        foundTotalPixel = false;
                    }
                }
            }

            //if (loading) {
            //if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) {
            if (!foundTotalPixel && totalMovedPixel >= totalPixel) {
                // loading = false;
                Log.v("...", "Last Item Wow !");
                Log.v("...", "totalMovedPixel !" + totalMovedPixel);

                // use this to turn auto-scrolling off:
                //mHandler.removeCallbacks(SCROLLING_RUNNABLE);
                marqueList.setAdapter(null);
                marqueList.setAdapter(new ScrollAdapter());
                pastVisiblesItems = visibleItemCount = totalItemCount = 0;
                totalMovedPixel = 0;

            }
        }
        // }
    });
    // use this to turn auto-scrolling on:
    mHandler.post(SCROLLING_RUNNABLE);
 }
}

2. SampleAdapter(ScrollAdapter.java)

package com.test.mo.test;

import android.support.v7.widget.RecyclerView;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

/**
 * Created by jovin.pj on 29-07-2015.
 */
public class ScrollAdapter extends  RecyclerView.Adapter<RecyclerView.ViewHolder> {

private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
private static final int TYPE_FOOTER = 2;


@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    RecyclerView.ViewHolder viewHolder = null;
    if (viewType == TYPE_ITEM) {
        //inflate your layout and pass it to view holder
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.scroll_child, parent, false);
        viewHolder = new ViewHolderItem(view);
    } else if (viewType == TYPE_HEADER || viewType == TYPE_FOOTER) {
        //inflate your layout and pass it to view holder
        //View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.header_footer, parent, false);
        View view = new View(parent.getContext());
        DisplayMetrics metrics = parent.getContext().getResources().getDisplayMetrics();
        int width = metrics.widthPixels;
        view.setLayoutParams(new LinearLayout.LayoutParams(width, LinearLayout.LayoutParams.WRAP_CONTENT));
        viewHolder = new ViewHolderHeaderOrFooter(view);
    }

    return viewHolder;
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

}

@Override
public int getItemCount() {
    // 1 for header and 1 for footer
    return 4 + 1 + 1;
}

@Override
public int getItemViewType(int position) {
    if (isPositionHeader(position))
        return TYPE_HEADER;
    else if (isPositionFooter(position))
        return TYPE_FOOTER;
    return TYPE_ITEM;
}


private boolean isPositionHeader(int position) {
    return position == 0;
}

private boolean isPositionFooter(int position) {
    return position == getItemCount() - 1;
}


// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public static class ViewHolderItem extends RecyclerView.ViewHolder {
    // each data item is just a string in this case
    public View mView;
    public ViewHolderItem(View v) {
        super(v);
        mView = v;
    }
}

// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public static class ViewHolderHeaderOrFooter extends RecyclerView.ViewHolder {
    // each data item is just a string in this case
    public View mView;
    public ViewHolderHeaderOrFooter(View v) {
        super(v);
        mView = v;
    }
 }
}

3. Main Activity's Layout file(main.xml)

<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:orientation="vertical">


 <android.support.v7.widget.RecyclerView
    android:id="@+id/marqueList"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    android:clipToPadding="false" />


</LinearLayout>

4. Adapter's Layout File(scroll_child.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:orientation="vertical" >

<TextView
    android:id="@+id/txtPercent"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:paddingLeft="30dp"
    android:paddingRight="30dp"
    android:text="0.14%" />

<View
    android:layout_width="match_parent"
    android:layout_height="1px"
    android:background="@android:color/darker_gray" />

<TextView
    android:id="@+id/txtData"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:paddingLeft="30dp"
    android:paddingRight="30dp"
    android:text="data" />

</LinearLayout>

increase or decrease pixelsToMove, this variables value to change the speed

Jovin
  • 164
  • 7
  • On click is not working after implementing your code – Abish R Jul 25 '16 at 17:42
  • @AbishR : how u implemented clicklistener ? is the clicklistener working without scrorlistener? – Jovin Jul 28 '16 at 11:34
  • Yes. It is working perfectly without the auto scrolling code which you shared top. OnClick is not working, I debugged. – Abish R Jul 30 '16 at 05:12
  • You fixed? Found the bug? Any other solution is there? – Abish R Aug 07 '16 at 10:57
  • yes. found the issue. I think it is because of android is not supporting click listener and scroll-listener simultaneously. – Jovin Aug 17 '16 at 10:25
  • Ok, Then any other way is there to achieve that.? Now I am changing the positions of item by selection. The item which I selected will come to first. Such kind of scrolling now I implemented. But, this not the thing I planned. If you found some solution please share. – Abish R Aug 18 '16 at 06:27
  • how to set vertical auto scroll view in above example – deep Oct 10 '16 at 06:59
  • @deep : did u try to set LayoutManager orientation to LinearLayoutManager.VERTICAL – Jovin Oct 12 '16 at 10:52
  • @Jovin Is there any way to achieve infinite scroll through this method? – Kartik Jan 30 '19 at 11:30
1

You can solve this using a library, MarqueeView which I have been using for some time and its works great. Here is the link for it.

Usage

In XML,

<asia.ivity.android.marqueeview.MarqueeView
        android:id="@+id/marqueeView150"
        android:layout_width="150dp"
        android:layout_height="wrap_content"
        marquee:speed="5"
        marquee:pause="1000"
        marquee:autoStart="true"
        >

        <TextView
            android:id="@+id/textView2"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do."
            android:textSize="20sp"
            android:textStyle="bold"
            android:singleLine="true"
            android:ellipsize="end"
            tools:ignore="HardcodedText"/>
    </asia.ivity.android.marqueeview.MarqueeView>

In Java,

final MarqueeView mv = (MarqueeView) findViewById(R.id.marqueeView100);
        mv.setPauseBetweenAnimations(500);
        mv.setSpeed(10);
        getWindow().getDecorView().post(new Runnable() {
            @Override
            public void run() {
                mv.startMarquee();
            }
        });

Please check this sample project for more help.

I would suggest you to use a single TextView inside the MarqueeView and combine both "percentage" and "data" into it.

You can absolutely customize the text size, font style, color, etc attributes of parts of a TextView using Spannable.

Here is a quick little example for you,

Spannable spn = (Spannable) tv3.getText();
        spn.setSpan(new BackgroundColorSpan(Color.RED), 0, 7,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        spn.setSpan(new StyleSpan(android.graphics.Typeface.BOLD_ITALIC),0, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

You can obviously customize it the way you want to give the percentage and the data a different look in a single TextView only.

You can also display images inside a TextView any where you want using ImageSpan,

Here is a quick example for it,

ImageSpan is = new ImageSpan(context, resId);
text.setSpan(is, index, index + strLength, 0);

Just ensure that you use correct index positions otherwise you will end up with an StringIndexOutOfBoundsException.

Aritra Roy
  • 14,384
  • 6
  • 63
  • 99
  • Thanks @Aritra Roy for your answer, can you please let me know how to make a textview including image or can you please help me in making such view? – Jignesh Jain Jul 24 '15 at 06:12
  • @JigneshJain I have updated the answer, please check. – Aritra Roy Jul 24 '15 at 06:19
  • thankx @AritraRoy for the update, i have updated the question. Please let me know is this feasible? If yes than how to do this? – Jignesh Jain Jul 24 '15 at 06:35
  • @JigneshJain I am sure this is what I explained in the long answer and have also updated the answer for a separate request. If this answer have helped you some how, shouldn't you accept the answer and reward the bounty? – Aritra Roy Jul 24 '15 at 06:54
  • We can move on with more enhancements and updates after that. – Aritra Roy Jul 24 '15 at 07:00
  • 1
    I don't have any problem rewarding you bounty, but atleast it should satisfy the one who is providing you bounty. You wrote it is possible to implement but still i didn't got the desired result. Please help me getting desired result, i'll surely reward you the bounty. Can you please provide me sample project. – Jignesh Jain Jul 24 '15 at 07:02
  • If it is possible please provide the sample with custom layout including the things i have provided in the image. the bounty will be yours for sure. – Jignesh Jain Jul 24 '15 at 07:03
  • But MarqueeView only supports child as textview. But i need to show custom layout. Can you please provide custom textview having image(arrow), custom text with percentage, a view below and text beneath that view. – Jignesh Jain Jul 24 '15 at 07:59
  • Also you did not read my question properly, i have asked to perform marquee effect from right inner side of screen to left inner side and your code performs marquee from the initial position where the static text is set. – Jignesh Jain Jul 24 '15 at 08:15