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