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.