最近一直巩固 Android 自定义 View 相关知识,之前都是阅读一些理论性的文章,不多抽时间本身去实现一个自定义 View,项目中遇到问题就上 github 上去找效果。其实自定义 View 涉及到不少内容,只有亲自动手完成几个案例,才能对相关知识点有深刻了解。android
本文是对上篇文章的一个补充,股票 APP 列表底部有一个实时更新交易的跑马灯效果,纵观市面上不少产品都应用到这个效果,决定本身动手实现一下。git
点击下载程序员
ViewFlipper 是 Android 中的基础控件,可能在通常开发中不多有人用到,因此不少开发者感受对这个控件很陌生,在控件圈里更远远没有 ViewPager 出名,可是 ViewFlipper 用法很简单,效果却很不错。github
ViewFlipper 继承自 ViewAnimator,而 ViewAnimator 又是继承自 FrameLayout,而 FrameLayout 就是平时基本上只显示一个子视图的布局,因为 FrameLayout 下很差肯定子视图的位置,因此不少状况下子视图以前存在相互遮挡,这样就形成了不少时候咱们基本上只要求 FrameLayout 显示一个子视图,而后经过某些控制来实现切换。正好,ViewFlipper 帮咱们实现了这个工做,咱们须要作的就是,选择恰当的时机调用其恰当的方法便可实质上只是封装了一些 ViewAnimator 的方法来调用,真正执行操做的是 ViewAnimator。web
方法 | 描述 |
---|---|
isFlipping | 判断 View 切换是否正在进行 |
setFilpInterval | 设置 View 之间切换的时间间隔 |
startFlipping | 开始 View 的切换,并且会循环进行 |
stopFlipping | 中止 View 的切换 |
setOutAnimation | 设置切换 View 的退出动画 |
setInAnimation | 设置切换 View 的进入动画 |
showNext | 显示 ViewFlipper 里的下一个 View |
showPrevious | 显示 ViewFlipper 里的上一个 View |
上面已经介绍了 ViewFlipper 控件基础知识,若是要实现跑马灯效果,建议自定义 ViewFlipper 实现本身的需求。本文使用自定义 ViewFlipper 的方式实现跑马灯垂直滚动效果。微信
设置如下属性,建议使用自定义属性方式,便于后期修改和 XML 中使用。编辑器
/** * 是否单行显示 */ private boolean isSingleLine; /** * 轮播间隔 */ private int interval = 3000; /** * 动画时间 */ private int animDuration = 1000; /** * 一次性显示item数目 */ private int itemCount = 1; 复制代码
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:duration="300" android:fromYDelta="100%p" android:toYDelta="0"/> <alpha android:duration="500" android:fromAlpha="0.0" android:toAlpha="1.0"/> </set> 复制代码
anim_marquee_out.xml 退出动画:ide
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:duration="400" android:fromYDelta="0" android:toYDelta="-100%p"/> <alpha android:duration="500" android:fromAlpha="1.0" android:toAlpha="0.0"/> </set> 复制代码
完成上面 2 步骤后,在自定义 ViewFlipper 中,完成动画的初始化工做。oop
private void initView(Context context) {
// 动画 Animation animIn = AnimationUtils.loadAnimation(context, R.anim.anim_marquee_in); Animation animOut = AnimationUtils.loadAnimation(context, R.anim.anim_marquee_out); // 设置动画 animIn.setDuration(animDuration); animOut.setDuration(animDuration); // 设置切换View的进入动画 setInAnimation(animIn); // 设置切换View的退出动画 setOutAnimation(animOut); // 设置View之间切换的时间间隔 setFlipInterval(interval); // 设置在测量时是考虑全部子项,仍是只考虑可见或不可见状态的子项。 setMeasureAllChildren(false); } 复制代码
由于跑马灯数据基本都是集合形式存在,因此采用 Adapter 模式,定义数据刷新回调接口 OnDataChangedListener,在 CustomizeMarqueeView 中接收回调并刷新数据。布局
public void setOnDataChangedListener(OnDataChangedListener onDataChangedListener) {
mOnDataChangedListener = onDataChangedListener; } public void notifyDataChanged() { if (mOnDataChangedListener != null) { mOnDataChangedListener.onChanged(); } } public interface OnDataChangedListener { void onChanged(); } 复制代码
定义建立子 View 布局方法和绑定数据方法
/** * @param parent * @return 自定义跑马灯的Item布局 */ public View onCreateView(CustomizeMarqueeView parent) { return LayoutInflater.from(parent.getContext()).inflate(R.layout.marqueeview_item, null); } /** * 更新数据 * @param view * @param position */ public void onBindView(View view, int position) { } 复制代码
根据 List 集合设置 View 数据,这里主要使用自定义 View 之自定义属性方式,主要分如下几个步骤:
private void setData() {
removeAllViews(); int currentIndex = 0; // 计算数据展现完毕须要几页,根据总条目%每页条目计算得出 int loopCount = mMarqueeViewBaseAdapter.getItemCount() % itemCount == 0 ? mMarqueeViewBaseAdapter.getItemCount() / itemCount : mMarqueeViewBaseAdapter.getItemCount() / itemCount + 1; // 遍历动态添加每页的View for (int i = 0; i < loopCount; i++) { // 每页单条展现 if (isSingleLine) { LinearLayout parentView = new LinearLayout(getContext()); parentView.setOrientation(LinearLayout.VERTICAL); parentView.setGravity(Gravity.CENTER); parentView.removeAllViews(); View view = mMarqueeViewBaseAdapter.onCreateView(this); parentView.addView(view); if (currentIndex < mMarqueeViewBaseAdapter.getItemCount()) {// 绑定View mMarqueeViewBaseAdapter.onBindView(view, currentIndex); } currentIndex = currentIndex + 1; addView(parentView); } else { LinearLayout parentView = new LinearLayout(getContext()); parentView.setOrientation(LinearLayout.VERTICAL); parentView.setGravity(Gravity.CENTER); parentView.removeAllViews(); // 每页显示多少条,就遍历添加几个子View for (int j = 0; j < itemCount; j++) { View view = mMarqueeViewBaseAdapter.onCreateView(this); parentView.addView(view); currentIndex = getRealPosition(j, currentIndex); if (currentIndex < mMarqueeViewBaseAdapter.getItemCount()) { mMarqueeViewBaseAdapter.onBindView(view, currentIndex); } } addView(parentView); } } } 复制代码
有的朋友会很好奇这跟 Activity 启动过程有什么关系?
由于 ViewFlipper 属性看到须要手动调用 startFlipping()方法和 stopFlipping()完成 View 切换和循环执行。因此考虑到 View 性能和使用效果,咱们重写了 View 的三个方法,实现开启和关闭。
onVisibilityChanged 是否调用,依赖于 View 是否执行过 onAttachedToWindow 方法。也就是 View 是否被添加到 Window 上。
onAttachedToWindow 方法是在 Activity resume 的时候被调用的,也就是 Activity 对应的 window 被添加的时候,且每一个 view 只会被调用一次,父 view 的调用在前,不论 view 的 visibility 状态都会被调用,适合作些 view 特定的初始化操做;
onDetachedFromWindow 方法是在 Activity destroy 的时候被调用的,也就是 Activity 对应的 window 被删除的时候,且每一个 view 只会被调用一次,父 view 的调用在后,也不论 view 的 visibility 状态都会被调用,适合作最后的清理操做;
@Override
protected void onVisibilityChanged(@NonNull View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); if (VISIBLE == visibility) { startFlipping(); } else if (GONE == visibility || INVISIBLE == visibility) { stopFlipping(); } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); startFlipping(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); stopFlipping(); } 复制代码
只须要在 XML 中加载自定义 View 布局,而后在 Activity 中获取 View,加载数据集合便可。
marquessViewAdapter = new MarquessViewAdapter(this);
mMarqueeView.setItemCount(1); mMarqueeView.setSingleLine(true); mMarqueeView.setAdapter(marquessViewAdapter); marquessViewAdapter.setMessageBeans(messageBeans); 复制代码
结合上一篇博文的最终效果图至上:
以上就完美实现了跑马灯效果,经过自定义 View 方式,结合动画属性。代码能够直接在项目中使用,只须要根据本身项目效果更改 item 的布局就好。本篇文章已是自定义 View 实战案例的第五篇,虽然都是一些简单效果,可是能将自定义 View 相关知识:View 绘制流程、View 测量、View 事件分发作一个系统化的深刻。但愿本文能对初学自定义 View 的朋友有所帮助。
个人微信:Jaynm888
欢迎点评,诚邀 Android 程序员加入微信交流群,公众号回复“加群”或者添加我微信拉你入群
”