Android 自定义View(五)实现跑马灯垂直滚动效果

1、前言

最近一直巩固 Android 自定义 View 相关知识,之前都是阅读一些理论性的文章,不多抽时间本身去实现一个自定义 View,项目中遇到问题就上 github 上去找效果。其实自定义 View 涉及到不少内容,只有亲自动手完成几个案例,才能对相关知识点有深刻了解。android

本文是对上篇文章的一个补充,股票 APP 列表底部有一个实时更新交易的跑马灯效果,纵观市面上不少产品都应用到这个效果,决定本身动手实现一下。git

2、开发准备工做

一、实现效果图

二、案例源码下载

点击下载程序员

三、案例应用知识点

  1. ViewFlipper 控件基础知识
  2. Android 动画基础知识
  3. 自定义 View 基础知识
  4. Activity 启动流程基础知识

3、ViewFlipper 介绍

一、ViewFlipper 定义

ViewFlipper 是 Android 中的基础控件,可能在通常开发中不多有人用到,因此不少开发者感受对这个控件很陌生,在控件圈里更远远没有 ViewPager 出名,可是 ViewFlipper 用法很简单,效果却很不错。github

ViewFlipper 继承自 ViewAnimator,而 ViewAnimator 又是继承自 FrameLayout,而 FrameLayout 就是平时基本上只显示一个子视图的布局,因为 FrameLayout 下很差肯定子视图的位置,因此不少状况下子视图以前存在相互遮挡,这样就形成了不少时候咱们基本上只要求 FrameLayout 显示一个子视图,而后经过某些控制来实现切换。正好,ViewFlipper 帮咱们实现了这个工做,咱们须要作的就是,选择恰当的时机调用其恰当的方法便可实质上只是封装了一些 ViewAnimator 的方法来调用,真正执行操做的是 ViewAnimator。web

二、ViewFlipper 相关属性介绍

方法 描述
isFlipping 判断 View 切换是否正在进行
setFilpInterval 设置 View 之间切换的时间间隔
startFlipping 开始 View 的切换,并且会循环进行
stopFlipping 中止 View 的切换
setOutAnimation 设置切换 View 的退出动画
setInAnimation 设置切换 View 的进入动画
showNext 显示 ViewFlipper 里的下一个 View
showPrevious 显示 ViewFlipper 里的上一个 View

4、代码实现

上面已经介绍了 ViewFlipper 控件基础知识,若是要实现跑马灯效果,建议自定义 ViewFlipper 实现本身的需求。本文使用自定义 ViewFlipper 的方式实现跑马灯垂直滚动效果。微信

一、自定义 ViewFlipper 属性

设置如下属性,建议使用自定义属性方式,便于后期修改和 XML 中使用。编辑器

/**  * 是否单行显示  */ private boolean isSingleLine; /**  * 轮播间隔  */ private int interval = 3000; /**  * 动画时间  */ private int animDuration = 1000; /**  * 一次性显示item数目  */ private int itemCount = 1; 复制代码

二、建立动画

  • anim_marquee_in.xml 进入动画:
    • Y 轴位置从下面 100%移动到位置 0,动画持续 300 毫秒
    • 渐变透明度动画效果由 0.0 到 1.0,动画持续 500 毫秒
<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

    • Y 轴位置从下面 0 移动到位置-100%,动画持续 400 毫秒
    • 渐变透明度动画效果由 1.0 到 0.0,动画持续 500 毫秒
<?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

由于跑马灯数据基本都是集合形式存在,因此采用 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 之自定义属性方式,主要分如下几个步骤:

  1. 根据集合 Size 和每页显示条目取余“%”计算一共须要展现几页;
  2. 遍历步骤 1 中获取的页数;
  3. 根据单行/多行显示,遍历每页建立子 View 布局;
  4. 调用 Adapter.onBindView()方法完成每一个子 View 数据绑定;
  5. addView()将全部子 View 添加到 ViewFlipper 中;
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 启动过程

有的朋友会很好奇这跟 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 状态都会被调用,适合作最后的清理操做;

  1. onAttachedToWindow 被调用,即表明着 View 被添加到了一个绘制过的视图树中。
  2. onAttachedToWindow 和 onDetachedFromWindow 能够被调用屡次。
  3. 当 View 被添加到已经绘制过的视图树上时,onAttachedToWindow 会被当即执行,接着 onVisibilityChanged 也会当即执行。
  4. 当 View 从视图上移除时,若是 onAttachedToWindow 方法曾经执行过,那么 onDetachedFromWindow 将会被执行。
  5. onVisibilityChanged 被调用的前提是 View 执行过 onAttachedToWindow 方法。
  6. 判断 View 是否执行过 onAttachedToWindow 的依据是 View 里的 mAttachInfo 对象不为空。
@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(); } 复制代码

七、Activity 中使用

只须要在 XML 中加载自定义 View 布局,而后在 Activity 中获取 View,加载数据集合便可。

marquessViewAdapter = new MarquessViewAdapter(this);
mMarqueeView.setItemCount(1); mMarqueeView.setSingleLine(true); mMarqueeView.setAdapter(marquessViewAdapter); marquessViewAdapter.setMessageBeans(messageBeans); 复制代码

结合上一篇博文的最终效果图至上:

5、总结

以上就完美实现了跑马灯效果,经过自定义 View 方式,结合动画属性。代码能够直接在项目中使用,只须要根据本身项目效果更改 item 的布局就好。本篇文章已是自定义 View 实战案例的第五篇,虽然都是一些简单效果,可是能将自定义 View 相关知识:View 绘制流程、View 测量、View 事件分发作一个系统化的深刻。但愿本文能对初学自定义 View 的朋友有所帮助。

个人微信:Jaynm888

欢迎点评,诚邀 Android 程序员加入微信交流群,公众号回复“加群”或者添加我微信拉你入群

相关文章
相关标签/搜索