Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android高級控件(六)——自定義ListView高仿一個QQ可拖拽列表的實現

Android高級控件(六)——自定義ListView高仿一個QQ可拖拽列表的實現

編輯:關於Android編程

我們做一些好友列表或者商品列表的時候,居多的需求可能就是需要列表拖拽了,而我們選擇了ListView,也是因為使用ListView太久遠了,導致對他已經有濃厚的感情了

而今天我們就來實現以下課拖拽的方案,大家可以借鑒以下

我們大致的思路,其實是這樣子的,也是我的設想,我們可以先去實現一個簡單的ListView的數據,但是他的Adapter,我們可以用系統封裝好的,然後傳遞進去一個實體類,最後自定義一個listview去操作,所以我們先把准備的工作做好,比如?

list_item.xml




    

    

這就只有一個頭像和一句話了,然後我們把實體類也給寫完了

DragBean

package com.liuguilin.draglistviewsample.entity;

/*
 *  項目名:  DragListViewSample 
 *  包名:    com.liuguilin.draglistviewsample.entity
 *  文件名:   DragBean
 *  創建者:   LGL
 *  創建時間:  2016/8/29 22:49
 *  描述:    實體類
 */

public class DragBean {

    private int ivId;
    private String text;

    public DragBean() {

    }

    public DragBean(int ivId, String text) {
        this.ivId = ivId;
        this.text = text;
    }

    public int getIvId() {
        return ivId;
    }


    public String getText() {
        return text;
    }

}

ok,其實很簡單,id是圖片,然後是文本,這樣我們就可以來實現一個Adapter了,這裡我用的是ArrayAdapter這樣能讓我們插入和刪除很輕松

DragAdapter

package com.liuguilin.draglistviewsample.adapter;

/*
 *  項目名:  DragListViewSample 
 *  包名:    com.liuguilin.draglistviewsample.adapter
 *  文件名:   DragAdapter
 *  創建者:   LGL
 *  創建時間:  2016/8/29 22:41
 *  描述:    拖拽列表的數據源
 */

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.liuguilin.draglistviewsample.R;
import com.liuguilin.draglistviewsample.entity.DragBean;

import java.util.List;

public class DragAdapter extends ArrayAdapter {

    /**
     * 構造方法
     *
     * @param context
     * @param mList
     */
    public DragAdapter(Context context, List mList) {
        super(context, 0, mList);
    }

    /**
     * 實現View
     *
     * @param position
     * @param convertView
     * @param parent
     * @return
     */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view;
        ViewHolder viewHolder;
        if (convertView == null) {
            view = View.inflate(getContext(), R.layout.list_item, null);
            viewHolder = new ViewHolder();
            viewHolder.imageView = (ImageView) view
                    .findViewById(R.id.iv_logo);
            viewHolder.textView = (TextView) view.findViewById(R.id.textView);
            view.setTag(viewHolder);
        } else {
            view = convertView;
            viewHolder = (ViewHolder) view.getTag();
        }

        viewHolder.imageView.setImageResource(getItem(position).getIvId());
        viewHolder.textView.setText(getItem(position).getText());
        return view;
    }

    /**
     * 緩存
     */
    static class ViewHolder {
        ImageView imageView;
        TextView textView;
    }

}

好的,其實到這裡,他就是一個最普通的ListView了,我們給他填充點數據

MainActivity

package com.liuguilin.draglistviewsample;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.liuguilin.draglistviewsample.adapter.DragAdapter;
import com.liuguilin.draglistviewsample.entity.DragBean;
import com.liuguilin.draglistviewsample.view.DragListView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    //列表
    private DragListView mListView;
    //數據
    private List mList = new ArrayList<>();
    //數據源
    private DragAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
    }

    /**
     * 初始化View
     */
    private void initView() {
        mListView = (DragListView) findViewById(R.id.mListView);

        //新增數據
        for (int i = 0; i < 30; i++) {
            DragBean bean = new DragBean(R.mipmap.ic_launcher, "劉某人程序員" + i);
            mList.add(bean);
        }
        //初始化數據源
        adapter = new DragAdapter(this,mList);
        mListView.setAdapter(adapter);
    }
}

現在可以看看實際的效果了

這裡寫圖片描述

現在我們可以重寫我們的ListView了

我們首先攔截他的事件

 /**
     * 獲取觸點所在條目的位置
     * 獲取選中條目的圖片
     * 事件的攔截機制
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //識別動作
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //獲取觸點的坐標
                int x = (int) ev.getX();
                int y = (int) ev.getY();
                //這樣就可以計算我按到哪個條目上了
                mStartPosition = mEndPosition = pointToPosition(x, y);
                //判斷觸點是否在logo的區域
                ViewGroup itemView = (ViewGroup) getChildAt(mStartPosition - getFirstVisiblePosition());
                //記錄手指在條目中的相對Y坐標
                dragPoint = y - itemView.getTop();
                //ListView在屏幕中的Y坐標
                dragOffset = (int) (ev.getRawY() - y);
                //拖動的圖標
                View dragger = itemView.findViewById(R.id.iv_logo);
                //判斷觸點是否在logo區域
                if (dragger != null && x < dragger.getRight() + 10) {
                    //定義ListView的滾動條目
                    //上
                    upScroll = getHeight() / 3;
                    //下
                    downScroll = getHeight() * 2 / 3;
                    //獲取選中的圖片/截圖
                    itemView.setDrawingCacheEnabled(true);
                    //獲取截圖
                    Bitmap bitMap = itemView.getDrawingCache();
                    //圖片滾動
                    startDrag(bitMap, y);
                }
                break;
        }
        //還會傳遞事件到子View
        return false;
    }

獲取到他的position之後我們直接截圖,並且顯示我們的window,這裡做的事情就比較多了我們還要判斷是否點擊的是頭像才去顯示window

 /**
     * 圖片在Y軸,也就是上下可滾動
     *
     * @param bitMap
     * @param y
     */
    private void startDrag(Bitmap bitMap, int y) {
        //窗體仿照
        wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        //設置窗體參數
        lParams = new WindowManager.LayoutParams();
        //設置寬高
        lParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        lParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        //屬性
        lParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
        //設置半透明
        lParams.alpha = 0.8f;
        //設置居中
        lParams.gravity = Gravity.TOP;
        //設置xy
        lParams.x = 0;
        lParams.y = y-dragPoint + dragOffset;
        //屬性
        lParams.format = PixelFormat.TRANSLUCENT;
        //設置動畫
        lParams.windowAnimations = 0;
        //圖片
        dragImageView = new ImageView(getContext());
        //設置截圖
        dragImageView.setImageBitmap(bitMap);
        //添加顯示窗體
        wm.addView(dragImageView, lParams);
    }

好的,我們初始化一下window,光顯示還不行呢,我們還要可以滑動,怎麼監聽?onTouchEvent的ACTION_MOVE事件是可以做到的

    /**
     * 觸摸事件
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        //錯誤的位置
        if (dragImageView != null && mEndPosition != INVALID_POSITION) {
            //在滑動事件中控制上下滑動
            switch (ev.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    //直接獲取到Y坐標進行移動
                    int moveY = (int) ev.getY();
                    doDrag(moveY);
                    break;
                //停止拖動成像
                case MotionEvent.ACTION_UP:
                    int upY = (int) ev.getY();
                    stopDrag();
                    onDrag(upY);
                    break;
            }
        }
        //攔截到事件
        return true;
    }

我們在移動的時候不斷的去更新他的位置

    /**
     * 控制窗體移動
     *
     * @param moveY
     */
    private void doDrag(int moveY) {
        if (dragImageView != null) {
            lParams.y = moveY - dragPoint + dragOffset;
            wm.updateViewLayout(dragImageView, lParams);
        }
        //判斷移動到分割線 返回-1
        int tempLine = pointToPosition(0, moveY);
        //我們處理他
        if (tempLine != INVALID_POSITION) {
            //只要你不移動到分割線 我才處理
            mEndPosition = tempLine;
        }

        //拖拽時滾動、滾動速度
        int scrollSpeed = 0;
        //上滾
        if (moveY < upScroll) {
            scrollSpeed = 10;
            //下滾
        } else if (moveY > downScroll) {
            scrollSpeed = -10;
        }
        //開始滾動
        if (scrollSpeed != 0) {
            //計算條目的Y坐標
            int dragItemY = getChildAt(mEndPosition - getFirstVisiblePosition()).getTop();
            //當前速度
            int dy = dragItemY + scrollSpeed;
            //設置
            setSelectionFromTop(mEndPosition, dy);
        }
    }

當你移動完成之後,我就可以停止你的window體了

     /**
     * 停止的位置
     */
    private void stopDrag() {
        //直接移除窗體
        if (dragImageView != null) {
            wm.removeView(dragImageView);
            dragImageView = null;
        }

    }

這樣,我就直接拼接你的position達到一個拖拽的效果

    /**
     * 最終開始成像
     *
     * @param upY
     */
    private void onDrag(int upY) {
        //分割線的處理
        //判斷移動到分割線 返回-1
        int tempLine = pointToPosition(0, upY);
        //我們處理他
        if (tempLine != INVALID_POSITION) {
            //只要你不移動到分割線 我才處理
            mEndPosition = tempLine;
        }

        /**
         * 你在最上方就直接落在第一個最下方就直接最下面一個
         */

        //上邊界處理
        if (upY < getChildAt(1).getTop()) {
            mEndPosition = 1;
            //下邊界處理
        } else if (upY > getChildAt(getChildCount() - 1).getTop()) {
            mEndPosition = getAdapter().getCount() - 1;
        }

        //開始更新item順序
        if (mEndPosition > 0 && mEndPosition < getAdapter().getCount()) {
            DragAdapter adapter = (DragAdapter) getAdapter();
            //刪除原來的條目
            adapter.remove(adapter.getItem(mStartPosition));
            //更新
            adapter.insert(adapter.getItem(mStartPosition), mEndPosition);
        }
    }

全部代碼貼上

DragListView

package com.liuguilin.draglistviewsample.view;


/*
 *  項目名:  DragListViewSample 
 *  包名:    com.liuguilin.draglistviewsample.view
 *  文件名:   DragListView
 *  創建者:   LGL
 *  創建時間:  2016/8/29 20:50
 *  描述:    自定義高仿QQ列表可拖拽的ListView
 */

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.ListView;

import com.liuguilin.draglistviewsample.R;
import com.liuguilin.draglistviewsample.adapter.DragAdapter;

public class DragListView extends ListView {

    //按下選中的position
    private int mStartPosition;
    //需要達到的position
    private int mEndPosition;
    //手指在條目中的相對Y坐標
    private int dragPoint;
    //ListView在屏幕中的Y坐標
    private int dragOffset;
    //上
    private int upScroll;
    //下
    private int downScroll;
    //窗體
    private WindowManager wm;
    //顯示的截圖
    private ImageView dragImageView;
    //窗體參數
    private WindowManager.LayoutParams lParams;

    //構造方法
    public DragListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 獲取觸點所在條目的位置
     * 獲取選中條目的圖片
     * 事件的攔截機制
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //識別動作
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //獲取觸點的坐標
                int x = (int) ev.getX();
                int y = (int) ev.getY();
                //這樣就可以計算我按到哪個條目上了
                mStartPosition = mEndPosition = pointToPosition(x, y);
                //判斷觸點是否在logo的區域
                ViewGroup itemView = (ViewGroup) getChildAt(mStartPosition - getFirstVisiblePosition());
                //記錄手指在條目中的相對Y坐標
                dragPoint = y - itemView.getTop();
                //ListView在屏幕中的Y坐標
                dragOffset = (int) (ev.getRawY() - y);
                //拖動的圖標
                View dragger = itemView.findViewById(R.id.iv_logo);
                //判斷觸點是否在logo區域
                if (dragger != null && x < dragger.getRight() + 10) {
                    //定義ListView的滾動條目
                    //上
                    upScroll = getHeight() / 3;
                    //下
                    downScroll = getHeight() * 2 / 3;
                    //獲取選中的圖片/截圖
                    itemView.setDrawingCacheEnabled(true);
                    //獲取截圖
                    Bitmap bitMap = itemView.getDrawingCache();
                    //圖片滾動
                    startDrag(bitMap, y);
                }
                break;
        }
        //還會傳遞事件到子View
        return false;
    }

    /**
     * 圖片在Y軸,也就是上下可滾動
     *
     * @param bitMap
     * @param y
     */
    private void startDrag(Bitmap bitMap, int y) {
        //窗體仿照
        wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        //設置窗體參數
        lParams = new WindowManager.LayoutParams();
        //設置寬高
        lParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        lParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        //屬性
        lParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
        //設置半透明
        lParams.alpha = 0.8f;
        //設置居中
        lParams.gravity = Gravity.TOP;
        //設置xy
        lParams.x = 0;
        lParams.y = y-dragPoint + dragOffset;
        //屬性
        lParams.format = PixelFormat.TRANSLUCENT;
        //設置動畫
        lParams.windowAnimations = 0;
        //圖片
        dragImageView = new ImageView(getContext());
        //設置截圖
        dragImageView.setImageBitmap(bitMap);
        //添加顯示窗體
        wm.addView(dragImageView, lParams);
    }

    /**
     * 觸摸事件
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        //錯誤的位置
        if (dragImageView != null && mEndPosition != INVALID_POSITION) {
            //在滑動事件中控制上下滑動
            switch (ev.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    //直接獲取到Y坐標進行移動
                    int moveY = (int) ev.getY();
                    doDrag(moveY);
                    break;
                //停止拖動成像
                case MotionEvent.ACTION_UP:
                    int upY = (int) ev.getY();
                    stopDrag();
                    onDrag(upY);
                    break;
            }
        }
        //攔截到事件
        return true;
    }

    /**
     * 最終開始成像
     *
     * @param upY
     */
    private void onDrag(int upY) {
        //分割線的處理
        //判斷移動到分割線 返回-1
        int tempLine = pointToPosition(0, upY);
        //我們處理他
        if (tempLine != INVALID_POSITION) {
            //只要你不移動到分割線 我才處理
            mEndPosition = tempLine;
        }

        /**
         * 你在最上方就直接落在第一個最下方就直接最下面一個
         */

        //上邊界處理
        if (upY < getChildAt(1).getTop()) {
            mEndPosition = 1;
            //下邊界處理
        } else if (upY > getChildAt(getChildCount() - 1).getTop()) {
            mEndPosition = getAdapter().getCount() - 1;
        }

        //開始更新item順序
        if (mEndPosition > 0 && mEndPosition < getAdapter().getCount()) {
            DragAdapter adapter = (DragAdapter) getAdapter();
            //刪除原來的條目
            adapter.remove(adapter.getItem(mStartPosition));
            //更新
            adapter.insert(adapter.getItem(mStartPosition), mEndPosition);
        }
    }

    /**
     * 停止的位置
     */
    private void stopDrag() {
        //直接移除窗體
        if (dragImageView != null) {
            wm.removeView(dragImageView);
            dragImageView = null;
        }

    }

    /**
     * 控制窗體移動
     *
     * @param moveY
     */
    private void doDrag(int moveY) {
        if (dragImageView != null) {
            lParams.y = moveY - dragPoint + dragOffset;
            wm.updateViewLayout(dragImageView, lParams);
        }
        //判斷移動到分割線 返回-1
        int tempLine = pointToPosition(0, moveY);
        //我們處理他
        if (tempLine != INVALID_POSITION) {
            //只要你不移動到分割線 我才處理
            mEndPosition = tempLine;
        }

        //拖拽時滾動、滾動速度
        int scrollSpeed = 0;
        //上滾
        if (moveY < upScroll) {
            scrollSpeed = 10;
            //下滾
        } else if (moveY > downScroll) {
            scrollSpeed = -10;
        }
        //開始滾動
        if (scrollSpeed != 0) {
            //計算條目的Y坐標
            int dragItemY = getChildAt(mEndPosition - getFirstVisiblePosition()).getTop();
            //當前速度
            int dy = dragItemY + scrollSpeed;
            //設置
            setSelectionFromTop(mEndPosition, dy);
        }
    }
}

然後我們引用

layout_main.xml




    


對了,別忘記了添加窗體的權限哦

 
 

做完這一系列事情之後,就覺得這個設想其實是對的,然後我們可以嘗試性的運行一下

這裡寫圖片描述

運行時成功的,說明我們的設想是沒有問題的,但是隨著而來的,也是諸多的bug,所以呢,這也只是思想的參考,不過這些bug我有時間野就修復一下,應該是可以的,好的,本篇博文就先到這裡!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved