The renderings that need to be realized are as follows

Introduction to Knowledge

To achieve this effect, you need to use the ViewFlipper class provided with the Android SDK. The translation is annotated as follows:

You can animate between two or more views that have been added to it. Only one child is shown at a time. Automatically switch between each child at regular intervals if required.

Let’s look at the diagram of this class

You can see from the diagram above that this class is a ViewGroup (almost zero presence). It felt like we could just put multiple TextViews into the container and make it look like an illustration (too easy).

ViewFlipper introduction

XML attributes

The attribute name explain
android:autoStart When true, animation starts automatically
android:flipInterval The time interval between views
android:inAnimation Enter the animation
android:outAnimation Leave the animation

Java method

Method names explain
isFlipping Check whether the View switch is in progress
setFilpInterval Set the time interval for switching between views
startFlipping Start the View switch, and it’s going to loop
stopFlipping Stop the View switch
setOutAnimation Set toggle View exit animation
setInAnimation Set toggle View entry animation
showNext Displays the next View in the View flipper
showPrevious Displays the previous View in the View flipper

ViewFlipper use

ViewFlipper layout

  <ViewFlipper
        android:id="@+id/viewflipper"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:flipInterval="2000"
        android:inAnimation="@anim/up_in"
        android:outAnimation="@anim/up_out"
        android:persistentDrawingCache="animation">

        <Button
            android:id="@+id/btn"
            android:layout_width="match_parent"
            android:layout_height="158dp"
            android:background="@color/blue"
            android:text="The first" />

        <Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="158dp"
            android:background="@color/red"
            android:text="The second" />


 </ViewFlipper>
Copy the code

2. Enter the slide out animation


      
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromYDelta="100%p"
        android:toYDelta="0%p"
        android:duration="1000" />
</set>


      
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="1000"
        android:fromYDelta="0%p"
        android:toYDelta="-100%p" />
</set>

Copy the code

Through the above 2 steps to achieve the basic version of the wheel broadcast small speaker function. This article would be boring if it ended there. Here is an advanced version

The advanced

The main problem is to directly add alternate display layout in the layout file, obviously this way is not flexible, scalability is poor. Here’s a simple encapsulation of this approach to quickly build display content without having to write large quantum layouts into the layout file. There is a kind of “Adapter” design mode we should all know about, in Android is the most common Adapter of RecyclerView. Again, we use the “adapter” design pattern to encapsulate this component. We can define a Adapter to convert the data that needs to be displayed into the View that needs to be displayed on the View, and then use the Adapter in the ViewFlipper to get the transformed View and dynamically add it to the ViewFlipper.

implementation

1, define a data Adapter MarqueeViewAdapter, imitate RecyclerView Adapter is defined as follows

public abstract class MarqueeViewAdapter<T> {
    protected List<T> mDatas;
    public MarqueeViewAdapter(List<T> datas) {
        this.mDatas = datas;
        if (datas == null) {
            throw new RuntimeException("MarqueeView datas is Null"); }}/** * set data */
    public void setData(List<T> datas) {
        this.mDatas = datas;
    }

    public int getItemCount(a) {
        return this.mDatas == null ? 0 : this.mDatas.size();
    }

    /** * Create an item view **/
    public abstract View onCreateView(XMarqueeView parent);

    /** * item view binds data **/
    public abstract void onBindView(View container, View view, int position);
}
Copy the code

There is also a problem here. When we set Data, there is no method to tell the layout to refresh the Data, so we need to define an interface to refresh the Data

public interface OnDataChangedListener {
    void onChanged(a);
}
Copy the code

The improved Adapter class is as follows

public abstract class MarqueeViewAdapter<T> {
    protected List<T> mDatas;
    private OnDataChangedListener mOnDataChangedListener;
    public MarqueeViewAdapter(List<T> datas) {
        this.mDatas = datas;
        if (datas == null) {
            throw new RuntimeException("MarqueeView datas is Null"); }}/** * set data */
    public void setData(List<T> datas) {
        this.mDatas = datas;
        this.notifyDataChanged();
    }

    public int getItemCount(a) {
        return this.mDatas == null ? 0 : this.mDatas.size();
    }

    /** * Create an item view **/
    public abstract View onCreateView(XMarqueeView parent);

    /** * item view binds data **/
    public abstract void onBindView(View container, View view, int position);
    
    public void setOnDataChangedListener(OnDataChangedListener onDataChangedListener) {
        this.mOnDataChangedListener = onDataChangedListener;
    }

    public void notifyDataChanged(a) {
        if (this.mOnDataChangedListener ! =null) {
            this.mOnDataChangedListener.onChanged(); }}}Copy the code

2. Then define our main character MarqueeView, which inherits ViewFlipper, and its main function is the encapsulation of ViewFlipper function. The attributes and functions provided are listed below

    private boolean enableAnimDuration;// Whether to use alternate animation
    private int interval;// Alternate interval times
    private int animDuration;// Animation length
    private int textSize;// Font size
    private int textColor;// Font color
Copy the code

Setting basic Properties

        Animation animIn = AnimationUtils.loadAnimation(context, R.anim.up_in);
        Animation animOut = AnimationUtils.loadAnimation(context, R.anim.up_out);
        if (this.enableAnimDuration) {
            animIn.setDuration((long)this.animDuration);
            animOut.setDuration((long)this.animDuration);
        }

        this.setInAnimation(animIn);
        this.setOutAnimation(animOut);
        this.setFlipInterval(this.interval);
        this.setMeasureAllChildren(false);
Copy the code

After completing the basic configuration of the ViewFlipper setup function above, it is time to wrap the data, first defining a method to configure the adapter

    public void setAdapter(MarqueeViewAdapter adapter) {
        if (adapter == null) {
            throw new RuntimeException("adapter must not be null");
        } else if (this.mMarqueeViewAdapter ! =null) {
            throw new RuntimeException("you have already set an Adapter");
        } else {
            this.mMarqueeViewAdapter = adapter;
            this.mMarqueeViewAdapter.setOnDataChangedListener(this);
            this.setData(); }}Copy the code

And then we get the adapter, View = Adapter.onCreateView (this); OnBindView (View, View, currentIndex); Bind the view. Then add the view to the MarqueeView.

The specific implementation

public class MarqueeView extends ViewFlipper implements OnDataChangedListener {
    private boolean enableAnimDuration = false;
    private int interval = 3000;
    private int animDuration = 1000;
    private int textSize = 14;
    private int textColor = Color.parseColor("# 888888");
    private MarqueeViewAdapter mMarqueeViewAdapter;
    private boolean isFlippingLessCount = true;

    public MarqueeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.init(context, attrs, 0);
    }

    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, styleable.XMarqueeView, defStyleAttr, 0);
        if(typedArray ! =null) {
            this.enableAnimDuration = typedArray.getBoolean(styleable.MarqueeView_isSetAnimDuration, false);
            this.isSingleLine = typedArray.getBoolean(styleable.MarqueeView_isSingleLine, true);
            this.isFlippingLessCount = typedArray.getBoolean(styleable.MarqueeView_isFlippingLessCount, true);
            this.interval = typedArray.getInteger(styleable.MarqueeView_marquee_interval, this.interval);
            this.animDuration = typedArray.getInteger(styleable.MarqueeView_marquee_animDuration, this.animDuration);
            if (typedArray.hasValue(styleable.MarqueeView_marquee_textSize)) {
                this.textSize = (int)typedArray.getDimension(styleable.MarqueeView_marquee_textSize, (float)this.textSize);
                this.textSize = Utils.px2sp(context, (float)this.textSize);
            }

            this.textColor = typedArray.getColor(styleable.MarqueeView_marquee_textColor, this.textColor);
            this.itemCount = typedArray.getInt(styleable.MarqueeView_marquee_count, this.itemCount);
            typedArray.recycle();
        }

        Animation animIn = AnimationUtils.loadAnimation(context, R.anim.up_in);
        Animation animOut = AnimationUtils.loadAnimation(context, R.anim.up_out);
        if (this.enableAnimDuration) {
            animIn.setDuration((long)this.animDuration);
            animOut.setDuration((long)this.animDuration);
        }

        this.setInAnimation(animIn);
        this.setOutAnimation(animOut);
        this.setFlipInterval(this.interval);
        this.setMeasureAllChildren(false);
    }

    public void setAdapter(MarqueeViewAdapter adapter) {
        if (adapter == null) {
            throw new RuntimeException("adapter must not be null");
        } else if (this.mMarqueeViewAdapter ! =null) {
            throw new RuntimeException("you have already set an Adapter");
        } else {
            this.mMarqueeViewAdapter = adapter;
            this.mMarqueeViewAdapter.setOnDataChangedListener(this);
            this.setData(); }}private void setData(a) {
        this.removeAllViews();
        int currentIndex = 0;
        int loopconunt = this.mMarqueeViewAdapter.getItemCount() 
        for(int i = 0; i < loopconunt; ++i) {
            View view = this.mMarqueeViewAdapter.onCreateView(this);
            if (currentIndex < this.mMarqueeViewAdapter.getItemCount()) {
                this.mMarqueeViewAdapter.onBindView(view, view, currentIndex);
            }
            ++currentIndex;
            this.addView(view);
        }

        if (this.isFlippingLessCount) {
            this.startFlipping(); }}public void setItemCount(int itemCount) {
        this.itemCount = itemCount;
    }

    public void setSingleLine(boolean singleLine) {
        this.isSingleLine = singleLine;
    }

    public void setFlippingLessCount(boolean flippingLessCount) {
        this.isFlippingLessCount = flippingLessCount;
    }

    public void onChanged(a) {
        this.setData();
    }

    protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        if (0 == visibility) {
            this.startFlipping();
        } else if (8 == visibility || 4 == visibility) {
            this.stopFlipping(); }}protected void onAttachedToWindow(a) {
        super.onAttachedToWindow();
        this.startFlipping();
    }

    protected void onDetachedFromWindow(a) {
        super.onDetachedFromWindow();
        this.stopFlipping(); }}Copy the code

use

1. Define the sublayout of the display


      
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	>

	<TextView
		android:id="@+id/textView"
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		android:ellipsize="end"
		android:gravity="center"
		android:maxLines="1"
		android:textColor="@color/color_gray_5"
		android:textSize="@dimen/dimen_13"
		/>
</LinearLayout>
Copy the code

2. Define adapter

public class MarqueeViewAdapter extends MarqueeViewAdapter<HeadLineBean.HeadLineItemBean> {
    private Context mContext;

    public MarqueeViewAdapter(List<HeadLineBean.HeadLineItemBean> datas, Context context) {
        super(datas);
        mContext = context;
    }

    @Override
    public View onCreateView(MarqueeView parent) {
        return LayoutInflater.from(parent.getContext()).inflate(R.layout.home_headline_marqueeview_item, null);
    }

    @Override
    public void onBindView(View parent, View view, final int position) {
        HeadLineBean.HeadLineItemBean bean = mDatas.get(position);
        TextView tvOne = view.findViewById(R.id.textView);
        tvOne.setText(bean.getTitle());
        tvOne.setGravity(Gravity.CENTER_VERTICAL|Gravity.LEFT);
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {}}); }}Copy the code

3. Define MarqueeView layout

<com.sakuqi.MarqueeView
                            android:id="@+id/notice_conatiner"
                            android:layout_width="wrap_content"
                            android:layout_height="match_parent"
                            android:layout_marginLeft="@dimen/dimen_6"
                            app:isSetAnimDuration="true"
                            app:marquee_animDuration="800"
                            app:marquee_interval="3000"
                            app:marquee_textColor="@color/white"
                            app:marquee_textSize="@dimen/dimen_12" />
Copy the code

4. Set the Adapter

MarqueeView marqueeView = getView().findViewById(R.id.notice_conatiner);
                    marqueeView.setVisibility(VISIBLE);
                    headLineItemBeanList.clear();
                    headLineItemBeanList.addAll(headLineBean.getData());
                    if (marqueeViewAdapter2 == null) {
                        marqueeViewAdapter2 = new MarqueeViewAdapter(headLineItemBeanList, context);
                        marqueeView.setAdapter(marqueeViewAdapter2);
                    } else {
                        marqueeViewAdapter2.setData(headLineItemBeanList);
                    }
Copy the code

conclusion

This custom View uses the adaptor’s design pattern to convert data into a View, which is then dynamically added to the ViewFlipper. If you don’t know the adapter design pattern, you can Google it for yourself.

Give this article a thumbs up if you find it helpful, or leave a comment below if you find anything wrong with it. Rome wasn’t built in a day. Make a little progress every day.