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 程序員加入微信交流羣,公衆號回覆「加羣」或者添加我微信拉你入羣

相關文章
相關標籤/搜索