Android 自定義View(三)實現體育賽事積分表效果

一、前言

自定義 View 是 Android 中高級工程師進階的必經之路,要想熟練掌握自定義 View 技能,View 繪製流程和 View 事件分發機制必須掌握的,開發過程中大多數情況下都能在網上找到類似的效果,可能修修改改也能滿足項目需求,但是一旦遇到比較棘手的問題,可能就會讓開發者很苦惱。

本篇文章是自定義 View 結合 View 事件分發實現一個賽事得分表效果。

如果對自定義 View 不熟悉的朋友可以參考以下文章:

二、準備工作

1、先看效果

在這裏插入圖片描述

2、案例源碼下載

下載源碼

3、應用知識點

  1. 自定義 View
  2. View 事件分發機制
  3. 設計模式-觀察者模式

4、思路分析

  1. 根據效果圖首先明確主頁面是一個列表;
  2. 頭部 Tab 欄可以水平滑動,所以父佈局使用 HorizontalScrollView 實現;
  3. 列表 Item 分 2 部分,一部分是[頭像和暱稱],另一部分是[積分];
  4. 頭部 Tab 欄水平滑動時,列表 Item 第二部分同步滑動;
  5. 熟悉事件分發的朋友都知道,看到這樣的佈局效果,肯定需要解決滑動衝突;

三、代碼實現

1、自定義橫向滾動控件

  • 因爲上面已經分析,頭部 Tab 欄父佈局和 item 第二部分同步滑動,所以都採用橫向滾動控件完成,所以自定義橫向滾動控件,繼承 HorizontalScrollView 並重寫構造方法,這裏重寫了 3 個構造方法。
public class CustomHScrollView extends HorizontalScrollView {

    ScrollViewObserver mScrollViewObserver = new ScrollViewObserver();

    public CustomHScrollView(Context context) {
        super(context);
    }

    public CustomHScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomHScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}
  • 定義外部可回調接口,當發生了滾動事件時接口,供外部訪問
public static interface OnScrollChangedListener {
    public void onScrollChanged(int l, int t, int oldl, int oldt);
}
  • 定義一個觀察者,用於滑動控件時,接收水平滑動的回調。並定義添加滾動監聽和移除滾動監聽的方法。
public static class ScrollViewObserver {
    List<OnScrollChangedListener> mChangedListeners;

    public ScrollViewObserver() {
        super();
        mChangedListeners = new ArrayList<OnScrollChangedListener>();
    }
    //添加滾動事件監聽
    public void AddOnScrollChangedListener(OnScrollChangedListener listener) {
        mChangedListeners.add(listener);
    }
    //移除滾動事件監聽
    public void RemoveOnScrollChangedListener(OnScrollChangedListener listener) {
        mChangedListeners.remove(listener);
    }
    //回調
    public void NotifyOnScrollChanged(int l, int t, int oldl, int oldt) {
        if (mChangedListeners == null || mChangedListeners.size() == 0) {
            return;
        }
        for (int i = 0; i < mChangedListeners.size(); i++) {
            if (mChangedListeners.get(i) != null) {
                mChangedListeners.get(i).onScrollChanged(l, t, oldl, oldt);
            }
        }
    }
}
  • 因爲父佈局是水平滾動 HorizontalScrollView,所以需要重寫 onScrollChanged()方法,監聽滑動變化。當觀察者 mScrollViewObserver 不爲 null 時,滑動監聽通知回調觀察者 ScrollViewObserver。
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    //滾動時通知
    if (mScrollViewObserver != null) {
        mScrollViewObserver.NotifyOnScrollChanged(l, t, oldl, oldt);
    }
    super.onScrollChanged(l, t, oldl, oldt);
}

以上水平滾動 HorizontalScrollView 基本上已經完成,上面預留外部訪問接口:OnScrollChangedListener,在 Tab 欄水平滑動時,可以在 Adapter 中獲取滾動水平、垂直滾動位置,來設置 item 中第二部分控件位置。

2、完善列表

列表頁採用 RecyclerView 完成,這部分不做過多描述,感興趣的朋友可以下載源碼查看。

  • 這裏對 RecyclerView.Adapter 中的處理,描述說明。在 ViewHolder 初始化控件時,將 Tab 欄的 ScrollView(被觀察者)和 item 中的 ScrollView(觀察者)完成訂閱。當 Tab 欄發生水平滾動時,實時通知觀察者完成界面刷新。
// item列表中的ScrollView
CustomHScrollView scrollView = itemView.findViewById(R.id.h_scrollView);
// Tab欄的ScrollView
CustomHScrollView headSrcrollView =  mHead.findViewById(R.id.h_scrollView);
// 訂閱
headSrcrollView.AddOnScrollChangedListener(new OnScrollChangedListenerImp(scrollView));
  • 在 Adapter 中新建 OnScrollChangedListenerImp 類,繼承自定義 CustomHScrollView 中的 OnScrollChangedListener 接口,上文提到,該接口是當發生了滾動事件時接口,供外部訪問。

  • 實現 onScrollChanged()方法,調用 HorizontalScrollView.smoothScrollTo()方法實現 item 中 HorizontalScrollView 滾動位置與 Tab 欄滾動實時同步效果。

需要注意的是,這裏需要記錄一下初始化位置,避免因刷新數據引起錯亂。

class OnScrollChangedListenerImp implements CustomHScrollView.OnScrollChangedListener {
    CustomHScrollView mScrollViewArg;

    public OnScrollChangedListenerImp(CustomHScrollView scrollViewar) {
        mScrollViewArg = scrollViewar;
    }

    @Override
    public void onScrollChanged(int l, int t, int oldl, int oldt) {
        mScrollViewArg.smoothScrollTo(l, t);
        //記錄滾動的起始位置,避免因刷新數據引起錯亂
        if (n == 1) {
            setPosData(oldl, oldt);
        }
        n++;
    }
}

3、解決滑動衝突

完成以上步驟後,我們發現,ListView 的垂直滑動和和 ListView 中 item 水平 ScrollView 滑動發生衝突,如果需要解決這個問題,就需要應用到我們 View 事件分發機制了,如果對事件分發機制和流程還不熟悉的朋友,可以先學習下。

  • 首先,當在表頭和 listView 控件上 Touch 時,我們都需要將事件分發給 ScrollView 處理,所以根據 View 事件分發機制,定義一個父佈局 InterceptScrollLinearLayout 繼承 LinearLayout,並且做時間攔截處理。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return true;
}
  • 然後在 Activity 中創建 OnTouchListener 子類,重寫 onTouch()方法,並且讓父控件 ListView 控件分別調用 setOnTouchListener()方法註冊回調函數,以便在觸摸事件發送到此視圖時調用。
class MyTouchLinstener implements View.OnTouchListener {

    @Override
    public boolean onTouch(View arg0, MotionEvent arg1) {
        //當在表頭和listView控件上touch時,將事件分發給 ScrollView
        mScrollView.onTouchEvent(arg1);
        return false;
    }
}
mHead.setOnTouchListener(new MyTouchLinstener());
recycler.setOnTouchListener(new MyTouchLinstener());

綜上,就已經完成文章開頭展示的效果圖樣式,希望本文對 Android 初學者有所幫助,在自定義 View 和 View 事件分發知識學習過程中可以有更深的理解。文章中只粘貼了部分代碼,需要完整代碼的可以下載代碼參考。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章