結合支付寶和微信首頁鞏固android事件分發機制 (附項目源碼)

来源:http://www.cnblogs.com/loveapple/archive/2016/06/06/5564190.html
-Advertisement-
Play Games

對android開發有一定瞭解的同學一定或多或少知道android的觸摸事件分發,整個事件的分發消耗流程都可以通過看源碼理解,下麵通過講解demo幫助加深事件分發的理解和在實戰中的應用。首先直接上demo截圖: ...


結合支付寶和微信首頁鞏固android事件分發機制

文章出處大黑的博客--事件分發

源碼地址 https://github.com/halibobo/TouchListenerConflict 歡迎star

android的事件分發和處理方式

對android開發有一定瞭解的同學一定或多或少知道android的觸摸事件分發,整個事件的分發消耗流程都可以通過看源碼理解,下麵通過講解demo幫助加深事件分發的理解和在實戰中的應用。首先直接上demo截圖:

enter image description here

demo佈局

整個首頁佈局是這樣的,最外層是ViewPager,裡面包含四個子功能,每個子功能的視圖都是一個Fragment。“功能1”里的列表項是一個GridView,此gridview外層是帶有LinearLayout的ScrollView。
佈局如下
enter image description here

難點分析和講解

一、GridView高度問題

二、長按GridView某一項可以替換位置,最後一項“更多”不參與滑動與替換位置

三、GridView長按並滑動某一項時,滑動過程中與ScrollView和ViewPager衝突問題

四、滑動GridView中某一項時,當滑動到頂部時ScrollView要能向下滾動;手指滑動到底部時要能項上滾動

解決問題

解決問題一

為瞭解決這一系列問題,我需要重寫DragGridView繼承GridView,第一步需要讓DragGridView的每一項item占滿整個視圖,而gridview預設是限定高度的,解決辦法是重寫onMeasure,修改測量高度方法,如下

 @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int expandSpec = View.MeasureSpec.makeMeasureSpec(
            Integer.MAX_VALUE >> 2, View.MeasureSpec.AT_MOST);//1.精確模式(MeasureSpec.EXACTLY) 2.最大模式(MeasureSpec.AT_MOST) 3.未指定模式(MeasureSpec.UNSPECIFIED)
    super.onMeasure(widthMeasureSpec, expandSpec);
}

解決問題二

現在我們需要長按GridView某一項可以替換位置,思路是首先需要捕捉到長按事件,那麼怎麼算長按事件呢?手指快速的從item上劃是不算的,手指按下然後很快離開這是點擊事件也不能算長按。只有手指在item上按下並且停留在item上一定時間才能算長按。有了思路,下麵看關鍵的幾處代碼:

private Handler mHandler = new Handler();
//用來處理是否為長按的Runnable
private Runnable mLongClickRunnable = new Runnable() {

    @Override
    public void run() {
    //todo
    }
};

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    switch(ev.getAction()){
        case MotionEvent.ACTION_DOWN:
            //使用Handler延遲dragResponseMS執行mLongClickRunnable
            mHandler.postDelayed(mLongClickRunnable, dragResponseMS);
            break;
        case MotionEvent.ACTION_MOVE:
            //如果我們在按下的item上面移動,只要不超過item的邊界我們就不移除mRunnable
            if(!isTouchInItem(mStartDragItemView, moveX, moveY)){
                mHandler.removeCallbacks(mLongClickRunnable);
                mHandler.removeCallbacks(mScrollRunnable);
            }

            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            mHandler.removeCallbacks(mLongClickRunnable);
            break;
    }
    return super.dispatchTouchEvent(ev);
}

解析: 定義一個Handler,重寫ViewGroup(這裡即DragGridView)分發事件方法dispatchTouchEvent(),在接受到ACTION DOWN事件時handler向消息隊列會延遲發送一條消息,延遲時間就是長按時間的閥值,當接受到消息時說明長按事件已成立,如果手指在消息延遲期間滑走或移出則取消消息。

有了長按事件如何滑動某一條item呢?如何替換兩個item位置呢?
這裡的解決思路是,當其中的某項假設是item1接收到長按事件後,先隱藏item1,在item1的位置上新建一個和item1長相位置都一樣view假設叫它litem,手指滑動時對litem跟隨滑動,接著重點來了,當手指滑動到item2區域後發出一個替換事件。這樣我們就成功將兩個item替換了位置並且用戶體驗比較好,代碼片段如下:

 //用來處理是否為長按的Runnable
private Runnable mLongClickRunnable = new Runnable() {

    @Override
    public void run() {
        if (onDragStartListener != null) {
            onDragStartListener.onDragStart();
        }
        isDrag = true; //設置可以拖拽
        mVibrator.vibrate(50); //震動一下
        mStartDragItemView.setVisibility(View.INVISIBLE);//隱藏該item
        //根據我們按下的點顯示item鏡像
        createDragImage(mDragBitmap, mDownX, mDownY);


    }
};
/**
 * 設置替換回調介面
 * @param onChangeListener
 */
public void setOnChangeListener(OnChangeListener onChangeListener){
    this.onChangeListener = onChangeListener;
}
/**
 * 創建拖動的鏡像
 * @param bitmap
 * @param downX
 *          按下的點相對父控制項的X坐標
 * @param downY
 *          按下的點相對父控制項的X坐標
 */
private void createDragImage(Bitmap bitmap, int downX , int downY){
    mWindowLayoutParams = new WindowManager.LayoutParams();
    mWindowLayoutParams.format = PixelFormat.TRANSLUCENT; //圖片之外的其他地方透明
    mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
    mWindowLayoutParams.x = downX - mPoint2ItemLeft + mOffset2Left;
    mWindowLayoutParams.y = downY - mPoint2ItemTop + mOffset2Top - mStatusHeight;
    mWindowLayoutParams.alpha = 0.55f; //透明度
    mWindowLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
    mWindowLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
    mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
            | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE ;

    mDragImageView = new ImageView(getContext());
    mDragImageView.setImageBitmap(bitmap);
    mWindowManager.addView(mDragImageView, mWindowLayoutParams);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
    if(isDrag && mDragImageView != null){
        switch(ev.getAction()){
            case MotionEvent.ACTION_MOVE:
                moveX = (int) ev.getX();
                moveY = (int) ev.getY();
                moveRawY = (int) ev.getRawY();
                //拖動item
                onDragItem(moveX, moveY);
                break;
            case MotionEvent.ACTION_UP:
                onStopDrag();
                isDrag = false;
                break;
        }
        return true;
    }
    return super.onTouchEvent(ev);
}

解析:在接收到長按事件後會調用 createDragImage(mDragBitmap, mDownX, mDownY)方法,這是為了在item的位置上創建一個長相一樣的view,當手指滑動時在onTouch()方法中監聽move事件處理view的位置

解決問題三

為題三的解決比較簡單,當gridview接收到長按事件後,處理ViewPager和ScrollView的方法一樣requestDisallowInterceptTouchEvent(false); 這個方法是讓ViewGroup本身不攔截觸摸事件。當gridView滑動結束在requestDisallowInterceptTouchEvent(true);

解決問題四

直接上源碼

public class ControlScrollView extends ScrollView {

private boolean isInControl = true;
private int moveSpeed = 5;
private final int msgWhat = 1;
private final int time = 20;
private ScrollState scrollState;

public ControlScrollView(Context context) {
    super(context);
    init();
}

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

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_MOVE:/**/
            if (!isInControl) {
                if (ev.getY() < 0) {
                    if (!myHandler.hasMessages(msgWhat)) {
                        Message msg = new Message();
                        msg.arg1 = -1;
                        msg.what = msgWhat;
                        myHandler.sendMessageDelayed(msg, time);
                    }
                    return super.dispatchTouchEvent(ev);
                } else if (ev.getY() > getHeight()) {
                    if (!myHandler.hasMessages(msgWhat)) {
                        Message msg = new Message();
                        msg.arg1 = 1;
                        msg.what = msgWhat;
                        myHandler.sendMessageDelayed(msg, time);
                    }
                    return super.dispatchTouchEvent(ev);
                } else {
                    myHandler.removeMessages(msgWhat);
                }
            } else {
                myHandler.removeMessages(msgWhat);
            }
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            if (scrollState != null) {
                scrollState.stopTouch();
            }
            myHandler.removeMessages(msgWhat);
            requestDisallowInterceptTouchEvent(false);
            break;
    }
    return super.dispatchTouchEvent(ev);
}


@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    myHandler.removeMessages(msgWhat);
}


private void init() {
    moveSpeed = ScreenUtils.dip2px(getContext(), moveSpeed);
}

public boolean isInControl() {
    return isInControl;
}

public ScrollState getScrollState() {
    return scrollState;
}

public void setScrollState(ScrollState scrollState) {
    this.scrollState = scrollState;
}

public void setInControl(boolean inControl) {
    isInControl = inControl;
}

private Handler myHandler = new Handler() {
    @Override
    public void dispatchMessage(Message msg) {
        super.dispatchMessage(msg);
        smoothScrollBy(0, moveSpeed * (msg.arg1 > 0 ? 1 : -1));
        Message msg1 = new Message();
        msg1.what = msg.what;
        msg1.arg1 = msg.arg1;
        myHandler.sendMessageDelayed(msg1, time);
    }
};

public interface ScrollState {
    void stopTouch();
}

}

解析:大致邏輯就是判斷手指位置,超出邊界發送消息對scrollview進行滾動

結束

源碼地址 https://github.com/halibobo/TouchListenerConflict 歡迎star

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 對象是js中的一種基本的數據類型,除了可以給自身附屬性外,javascript對象還可以從一個稱為原型的對象繼承屬性。這種“原型式繼承”是javascript的核心特征。 在javascript中,創建一個對象通常可以有三個方法,對象直接量,關鍵字new和Object.create()函數。接下來會 ...
  • 1.escape() 不能直接用於URL編碼,它的真正作用是返回一個字元的Unicode編碼值。比如“春節”的返回結果是%u6625%u8282,escape()不對"+"編碼主要用於漢子編碼,現在已經不提倡使用了。 2.encodeURI()是javascript中真正用來對URL編碼的函數。編碼 ...
  • Node.js是一個基於Chrome JavaScript運行時建立的平臺,小巧方便搭建。運行的埠可以在瀏覽器上運行,顯示效果,但每次用瀏覽器也挺麻煩,我們這裡講的是在sublime text2中配置,是的js在sublimetext2中運行,再也不用切換到瀏覽器了。 1.首先安裝node.js, ...
  • ...
  • 下拉框二級聯動效果在日常應用場景中經常會碰到,尤其是涉及地區、品種等有多級選項時。例如:常見的省市聯動下拉框,在選擇省份時,城市列表也會更隨改變。 思路: 1,所謂聯動效果,是指出發父級的數據變化時,會影響到關聯性子級數據元素的變化。 下麵是造的省市的數據: var linkDatas = { pr ...
  • HTML5是最新的HTML標準,或遲或早,所有的web程式員都會發現需要使用到這個最新的標準,而且,很多人都會感覺到,重新開發一個HTML5的網站,要比把一個網站從HTML4遷移到HTML5上容易的多,這是因為這兩個版本之間有很大不同之處。 事實上,HTML5並沒有對HTML4做什麼重大的修改,它們 ...
  • iOS FFmpeg 優秀博客集錦 這篇博客沒有我自己寫的內容: 主要是對FFmpeg一些優秀博客的記錄 隨時更新 1 "iOS編譯FFmpeg、kxmovie實現視頻播放" ...
  • iOS的觸摸事件個人總結,分為兩步: 第一步:是找到哪個視圖上觸摸 第二步:分析由誰去響應(響應者連) 1.尋找被觸摸的視圖原理如下圖 hitText:withEvent:的方法處理流程: 首先會在當前視圖view上調用pointInside:withEvent:方法來判斷觸摸事件是否存在當前vie ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...