模仿qq列表信息滑動刪除效果

来源:http://www.cnblogs.com/r-decade/archive/2017/01/08/6262094.html
-Advertisement-
Play Games

這個效果的完成主要分為兩個部分 1. 自定義view作為listview的列表項 一個view裡面包括 顯示頭像,名字,消息內容等的contentView和滑動才能顯示出來的刪除,置頂的右邊菜單menuView 在手指移動的時候同時改變這兩個視圖的位置 2. 重寫listview 判斷item向左還 ...


這個效果的完成主要分為兩個部分

  1. 自定義view作為listview的列表項 一個view裡面包括 顯示頭像,名字,消息內容等的contentView和滑動才能顯示出來的刪除,置頂的右邊菜單menuView 在手指移動的時候同時改變這兩個視圖的位置

  2. 重寫listview 判斷item向左還是向右滑動 正常的滾動還是左右滑動等等 重寫onTouchEvent 進行事件分發

大致思路:

listview進行事件分發,判斷需要滑動還是滾動等狀態,如果需要滑動將事件傳遞給item進行滑動處理. 在item中控制contentView和menuView進行位置的變化完成滾動效果

重寫listview代碼
public class SlideListView extends ListView{

    private SlideItem mTouchView=null;//記錄當前點擊的item View
    private float mDownX;//x軸坐標
    private float mDownY;//y軸坐標
    private int mTouchState;//記錄點擊狀態
    private int mTouchPosition;//記錄點擊位置
    private static final int TOUCH_STATE_NONE=0; //按下狀態
    private static final int TOUCH_STATE_X=1;//橫滑狀態
    private static final int TOUCH_STATE_Y=2;//豎滑狀態
    //判斷橫豎滑動的最小值
    private static final int MAX_Y=5;
    private static final int MAX_X=3;

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

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

    public SlideListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView == null)
            return super.onTouchEvent(ev);

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //按住的item的position
                int oldPosition = mTouchPosition;
                //記錄位置
                mDownX = ev.getX();
                mDownY = ev.getY();
                mTouchState = TOUCH_STATE_NONE;
                //根據當前橫縱坐標點獲取點擊的item的position
                mTouchPosition = this.pointToPosition((int) ev.getX(), (int) ev.getY());

                //判斷當前點擊的是否和上次點擊的item是同一個,如果是同一個,並且狀態是打開了的就記錄狀態和坐標
                //記錄坐標通過Item中的downX屬性
                if (mTouchPosition == oldPosition && mTouchView != null && mTouchView.isOpen()) {
                    mTouchState = TOUCH_STATE_X;
                    mTouchView.onSwipe(ev);
                    return true;
                }
                //獲取當前的item的View
                View currentView = getChildAt(mTouchPosition - getFirstVisiblePosition());
                //如果不是同一個item 那麼點擊的話就關閉掉之前打開的item
                if (mTouchView != null && mTouchView.isOpen()) {
                    mTouchView.smoothCloseMenu();
                    mTouchView = null;
                    return super.onTouchEvent(ev);
                }
                //判斷該view的類型
                if (currentView instanceof SlideItem) {
                    mTouchView = (SlideItem) currentView;
                }
                if (mTouchView != null) {
                    mTouchView.onSwipe(ev);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                float dy = Math.abs((ev.getY() - mDownY));
                float dx = Math.abs((ev.getX() - mDownX));
                if (mTouchState == TOUCH_STATE_X) {
                    if (mTouchView != null) {
                        //執行滑動
                        mTouchView.onSwipe(ev);
                    }
                    return true;
                } else if (mTouchState == TOUCH_STATE_NONE) {
                    //判斷滑動方向,x方向執行滑動,Y方向執行滾動
                    if (Math.abs(dy) > MAX_Y) {
                        mTouchState = TOUCH_STATE_Y;
                    } else if (dx > MAX_X) {
                        mTouchState = TOUCH_STATE_X;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                //判斷狀態
                if (mTouchState == TOUCH_STATE_X) {
                    if (mTouchView != null) {
                        mTouchView.onSwipe(ev);
                        //如過最後狀態是打開 那麼就重新初始化
                        if (!mTouchView.isOpen()) {
                            mTouchPosition = -1;
                            mTouchView = null;
                        }
                    }
                    ev.setAction(MotionEvent.ACTION_CANCEL);
                    super.onTouchEvent(ev);
                    return true;
                }
                break;
        }
        return super.onTouchEvent(ev);
    }
}
重寫item項

view的滑動效果都是在里完成的 使用了Scroller類

關於Scroller的使用文章最後已經粘出了大神的帖子 不懂的同學可以先把Scroller的使用理解了在看這個滑動效果就很好懂了 我在這裡簡單講講

這個類的並沒有實際的完成滾動效果 它是一個計算控制項移動軌跡的輔助類,
比如說:在1秒內從位置0移動到位置100 這個類會計算出移動的數值,它並沒有完成滑動的效果,但是告訴了我們這個滑動的過程 實際的上的view移動操作在computeScroll()完成 這個方法是view的自帶方法 需要我們重寫

computeScroll方法又是怎麼情況呢 看源碼 本身是個空的 就等著我們實現 我們實際改變view位置的代碼就是在此方法內調用的

額。。。英語一般
大致意思 我們要通過Scroller實現一個滾動效果的時候 父佈局就會調用此方法來完成子視圖的位置更新

官方的描述是:當我們執行ontouch或invalidate()或postInvalidate()都會導致這個方法的執行

在此方法中不斷的獲取到移動的距離 通過view自帶的layout()方法更新view所在位置

    /**
     * Called by a parent to request that a child update its values for mScrollX
     * and mScrollY if necessary. This will typically be done if the child is
     * animating a scroll using a {@link android.widget.Scroller Scroller}
     * object.
     */
    public void computeScroll() {
    }
public class SlideItem extends LinearLayout {
    private View contentView = null; //不滑動顯示的view
    private View menuView = null; //左滑顯示的view

    //計算滑動 動畫效果
    private Scroller mOpenScroller;
    private Scroller mCloseScroller;

    private int downX; //開始按下的位置

    //記錄狀態
    private int state = STATE_CLOSE;
    private static final int STATE_CLOSE = 0;
    private static final int STATE_OPEN = 1;

    private int mBaseX;//在關閉滑動的時候計算與父佈局的剩餘距離


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

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

    public SlideItem(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setContentView(View contentView, View rightView){

        this.contentView = contentView;
        this.menuView = rightView;

        //初始化mColoseScroller和mOpenScroller
        mCloseScroller=new Scroller(getContext());
        mOpenScroller = new Scroller(getContext());

        initView();
    }
    //child view的佈局參數設定好後 添加到parent view裡面
    private void initView() {
        //這是設置寬和高
        LayoutParams contentParams = new LayoutParams
                (LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        LayoutParams rightParams=new LayoutParams
                (LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
        contentView.setLayoutParams(contentParams);
        contentView.setPadding(10,10,10,10);
        menuView.setLayoutParams(rightParams);
        this.addView(contentView);
        this.addView(menuView);
    }

    // 判斷是否滑出的狀態
    public boolean isOpen() {
        return state == STATE_OPEN;
    }

    /**
     * 供listView調用 進行視圖的移動   listView判斷狀態 什麼情況下左滑
     * @param event
     * @return
     */
    public boolean onSwipe(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                //按下位置減去移動位置 獲取移動的距離
                int dis = (int) (downX - event.getX());
                if (state == STATE_OPEN) {
                    dis += menuView.getWidth();
                }
                //移動
                move(dis);
                break;
            case MotionEvent.ACTION_UP:
                //當滑到右邊視圖一半的距離 自動滑進滑出
                if ((downX - event.getX()) > (menuView.getWidth() / 2)) {
                    smoothOpenMenu();
                } else {
                    smoothCloseMenu();
                    return false;
                }
                break;
        }
        //消費掉事件
        return true;
    }

    /**
     * 視圖重新繪製時調用
     */
    @Override
    public void computeScroll() {
        if (state == STATE_OPEN) {
            //computeScrollOffset滑動是否結束
            if (mOpenScroller.computeScrollOffset()) {
                move(mOpenScroller.getCurrX());
                postInvalidate();
            }
        } else {
            if (mCloseScroller.computeScrollOffset()) {
                move(mBaseX - mCloseScroller.getCurrX());
                postInvalidate();
            }
        }
    }

    /**
     * 移動視圖
     * @param dis
     */
    private void move(int dis) {
        //這兩個判斷是為了保證 不要把視圖移動過多 導致視圖偏移
        if (dis > menuView.getWidth()) {
            dis = menuView.getWidth();
        }
        if (dis < 0) {
            dis = 0;
        }
        //view.layout()控制view相對於其父佈局的位置   在觸發移動的時候調用不斷改變位置 完成實際的滑動效果
        contentView.layout(-dis, contentView.getTop(), contentView.getWidth() - dis, getMeasuredHeight());
        menuView.layout(contentView.getWidth() - dis, menuView.getTop(), contentView.getWidth() + menuView.getWidth() - dis, menuView.getBottom());
    }

    /**
     * 滑動關閉
     * contentView.getLeft()  與其父視圖的相對位置
     */
    public void smoothCloseMenu() {
        state = STATE_CLOSE;
        mBaseX = -contentView.getLeft();
        mCloseScroller.startScroll(0, 0, mBaseX, 0, 350);
        postInvalidate();
    }

    /**
     * 滑動打開
     */
    public void smoothOpenMenu() {
        state = STATE_OPEN;
        mOpenScroller.startScroll(-contentView.getLeft(), 0, menuView.getWidth(), 0, 350);
        postInvalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if(menuView != null)
            menuView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //確保centerView menuView的顯示位置
        if(contentView != null)
            contentView.layout(0, 0, getMeasuredWidth(), contentView.getMeasuredHeight());
        if(menuView != null)
            menuView.layout(getMeasuredWidth(), 0, getMeasuredWidth() + menuView.getMeasuredWidth(), contentView.getMeasuredHeight());
    }
}
適配器
public class SlideAdapter extends BaseAdapter implements View.OnClickListener{

    private List<String> dataList;
    private Context context;
    private LayoutInflater inflater;
    public SlideAdapter(Context context, List<String> dataList) {
        this.context = context;
        this.dataList = dataList;
        this.inflater=LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return 5;
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder=null;
        if (convertView==null){
            View content=inflater.inflate(R.layout.adapter_item_content,null);
            View menu=inflater.inflate(R.layout.adapter_item_menu,null);
            holder=new ViewHolder(content,menu);
            SlideItem slideItem=new SlideItem(context);
            slideItem.setContentView(content,menu);
            convertView=slideItem;
            convertView.setTag(holder);
        }else {
            holder= (ViewHolder) convertView.getTag();
        }
        holder.itemTvDelete.setOnClickListener(this);
        holder.itemTvNoRead.setOnClickListener(this);
        holder.itemTvToTop.setOnClickListener(this);
        return convertView;
    }

    class ViewHolder{
        TextView itemTvToTop;
        TextView itemTvNoRead;
        TextView itemTvDelete;

        public ViewHolder(View center,View menu) {
            this.itemTvToTop = (TextView) menu.findViewById(R.id.item_to_top);
            this.itemTvNoRead = (TextView) menu.findViewById(R.id.item_no_read);
            this.itemTvDelete = (TextView) menu.findViewById(R.id.item_delete);
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.item_no_read:
                Toast.makeText(context,"標為未讀",Toast.LENGTH_SHORT).show();
                break;
            case R.id.item_to_top:
                Toast.makeText(context,"置頂了熬",Toast.LENGTH_SHORT).show();
                break;
            case R.id.item_delete:
                Toast.makeText(context,"刪除啦",Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

參考文檔:
SwipeMenuListView github上的實現此效果的開源項目
Scroller的使用



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

-Advertisement-
Play Games
更多相關文章
  • 前言 最近項目集成了Tinker,開始認為集成會比較簡單,但是在實際操作的過程中還是遇到了一些問題,本文就會介紹在集成過程大家基本會遇到的主要問題。 考慮一:後臺的選取 目前後臺功能可以通過三種方式實現: 1、自己搭建後臺布丁下發系統2、第三方提供的服務,目前如原微信simsun大神的個人tinke ...
  • 無論是哪種交易軟體,對於程式員來講,最麻煩的就是去實現各種演算法。本文以SAR演算法的實現過程為例,為大家說明如何使用Warensoft Stock Service來實現高頻交易軟體的快速開發。 目前WarensoftStockService已經實現了C# 版本的客戶端驅動,可以直接在Nuget上搜索... ...
  • 以下內容為原創,歡迎轉載,轉載請註明 來自天天博客: 在Dagger 2中Activities和Subcomponents的多綁定 原文: 幾個月前,在 "MCE^3" 會議中,Gregory Kick在他的 "演講" 中展示了一個提供Subcomponents(比如,為Activity)的新概念。 ...
  •   現在很多程式都開始使用Swift開發了,但是第三方庫大多數都是用OC寫的,所以我們要使用Swift和OC混編。今天的內容主要講Swift3.0集成極光推送。 1.準備工作    "集成指南" ,極光上說的都很清楚,把創建應用和配置工程實現。 "SDK下載地 ...
  • 開發環境是OS X系統下的Xcode Xcode的兩個快捷鍵以及打開Xcode項目的正確方式 Xcode的兩個快捷鍵以及打開Xcode項目的正確方式 代碼的實時檢測和手動編譯鏈接的區別(command + B) 代碼實時檢測: 不是對代碼的編譯,是xcode的一個智能的功能,有時候不准確 手動編譯鏈 ...
  • 最近項目設計到App抓包,所以採用Fiddler工具來採集獲取APP數據包,但是fiddler對有些app是無法捕獲到數據包的,以下是我的處理方法: 1. 我預設代理埠使用的是自定義的埠而不是預設的8888埠; 2. 手機端安裝Fiddler證書,電腦端關閉防火牆 對我採集的app來說親測有效 ...
  • Your project path contains non-ASCII characters 錯誤原因:引用項目的路徑中包含中文 解決方法:重新新建一個項目,項目的路徑為英文。2:把現有的項目的路徑修改為不包含英文的。 ...
  • DDProgressHUD的介紹 提供了四種類型的展示: 顯示無限旋轉的載入圖(比如小菊花,可以自定義),顯示文字信息。網路刷新時經常用到。 顯示載入進度的動畫,也可以顯示文字。網路下載時用的比較多,載入網頁時也可以用。 與用戶彈窗交互的彈窗,告知用戶當前操作的狀態,成功還是失敗,顯示一張圖片和文字 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...