Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android編程入門 >> Android開發學習之路-PopupWindow和仿QQ左滑刪除

Android開發學習之路-PopupWindow和仿QQ左滑刪除

編輯:Android編程入門

這周作業,要做一個類似QQ的左滑刪除效果的ListView,因為不想給每個item都放一個按鈕,所以決定用PopupWindow,這裡記錄一下

先放一下效果圖:

先說明一下這裡面的問題:

①沒有做到像QQ那樣可以允許item跟隨手指移動,雖然PopupWindow有update方法讓我們動態移動,但是在屏幕外移動會沒有動畫效果,直接彈進來

②仔細觀察可以發現,item的滑動和刪除按鈕的滑動是分開的,無法保證它們會一起播放,QQ的動畫可以

再說說大概的思路,因為我們沒有讓item都帶上Button,所以用到了PopupWindow來做刪除按鈕,我們可以通過自定義ListView來監聽手勢,如果判斷了是左滑動,則讓PopupWindow動態顯示出來,在讓item做一個平移的效果,當兩者的持續時間一致的時候,效果就出來了。接下來,如果用戶點擊了PopupWindow以外的區域,PopupWindow會消失,回調dismiss方法,我們再把item移回原位。

 

介紹一下PopupWindow,它允許我們在屏幕上的任意位置彈出一個提示框,提示框的布局我們可以自己定義,我們上面的刪除按鈕,其實就是一個PopupWindow。

如果我們要使用PopupWindow,可以像下面這樣,定義一個自定義布局的PopupWindow

View view = LayoutInflater.from(context).inflate(R.layout.item_delete, null);
PopupWindow mPopupWindow = new PopupWindow(view, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, true);

如果我們要顯示這個PopupWindow,可以調用showAtLocation或者showAsDropDown,前者是直接把PopupWindow顯示在某個特定位置,後者是在某個位置從上向下彈出,這裡我們調用第一個方法,有興趣的可以嘗試下第二個,參數什麼的可以不用管,不懂的看看API就知道了,之後再調用update方法更新位置信息

mPopupWindow.showAtLocation(itemView, Gravity.LEFT | Gravity.TOP, locations[0]+ itemView.getWidth() - popWidth, locations[1]);
mPopupWindow.update();

至於對PopupWindow設置背景什麼的,需要的時候看看API就知道了!這裡不詳說。

 

至於效果的實現,我們一步一步的來。

①自定義一個ListView,詳細的做法,已經在下面做了注釋

public class SlidableListView extends ListView {
    private static final String TAG = "SlidableListView";
    private int touchSlop;

    private View itemView; // 每個Item的View
    private int mCurrentDownPosition; // 當前點擊的位置,用來找出點擊的Item
    private PopupWindow mPopupWindow; // 刪除按鈕
    private int popHeight, popWidth; // 刪除按鈕的高和寬
    private boolean isSliding = false; // 手指是否在向左滑動
    private int downX, downY, moveX, moveY; // 點擊的X和Y,以及移動的X和Y
    private OnItemDeleteListener onItemDeleteListener; // 點擊刪除按鈕時候的回調接口

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

    // 在布局中使用自定義控件調用的構造函數,這裡進行一些必要的初始化
    public SlidableListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 初始化刪除按鈕,記得讓按鈕可以接收焦點
        View view = LayoutInflater.from(context).inflate(R.layout.item_delete, null);
        mPopupWindow = new PopupWindow(view, WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT, true);
        // 獲取刪除按鈕的寬和高
        view.measure(0, 0);
        popWidth = view.getMeasuredWidth();
        popHeight = view.getMeasuredHeight();
        // 獲取滑動的最小距離
        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

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

    // 先攔截touch事件,根據不同情況進行分發
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        int x = (int) ev.getX(), y = (int) ev.getY();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                downX = x;
                downY = y;
                // 如果刪除按鈕正在顯示,那麼不處理這個觸摸事件,並把按鈕隱藏起來
                if (mPopupWindow.isShowing()) {
                    mPopupWindow.dismiss();
                    return false; // 阻止事件傳遞
                }
                mCurrentDownPosition = pointToPosition(downX, downY);
                // 獲取觸摸的item
                itemView = getChildAt(mCurrentDownPosition - getFirstVisiblePosition());
                break;
            case MotionEvent.ACTION_MOVE:
                moveX = x;
                moveY = y;
                int deltaX = moveX - downX;
                int deltaY = moveY - downY;
                // 當判斷為左滑的時候,把事件交給OnTouchEvent來處理
                if (moveX < downX && Math.abs(deltaX) > touchSlop && Math.abs(deltaY) <
                        touchSlop) {
                    isSliding = true;
                }
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        // 判斷是否已經有左右滑動
        if (isSliding) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    isSliding = false;
                    break;
                case MotionEvent.ACTION_MOVE:
                    // 獲取item在屏幕中的位置信息
                    int[] locations = new int[2];
                    itemView.getLocationOnScreen(locations);
                    // 給刪除按鈕設置動畫,這裡自定了出現從右到左平移,隱藏從左到右
                    mPopupWindow.setAnimationStyle(R.style.PopupWindowAnimations);
                    // 設置刪除按鈕的位置,這裡設置在item的最右端
                    mPopupWindow.showAtLocation(itemView, Gravity.LEFT | Gravity.TOP, locations[0]
                            + itemView.getWidth() - popWidth, locations[1]);
                    mPopupWindow.update();
                    // 給item播放平移動畫,配合刪除按鈕
                    ObjectAnimator.ofFloat(itemView, "translationX", 0, -popWidth).setDuration
                            (200).start();
                    // 獲取刪除按鈕的實例,這裡是一個TextView
                    TextView delete = (TextView) mPopupWindow.getContentView().findViewById(R.id
                            .delete);
                    // 設置監聽,如果刪除按鈕消失(點擊按鈕外的地方,按下back鍵)回調
                    mPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
                        @Override
                        public void onDismiss() {
                            // 把item移回原位置
                            ObjectAnimator.ofFloat(itemView, "translationX", -popWidth, 0)
                                    .setDuration(200).start();
                        }
                    });
                    // 給刪除按鈕設置點擊事件
                    delete.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            // 獲取點擊的item位置,並調用回調接口方法發送出去,隱藏刪除按鈕
                            int position = SlidableListView.this.getPositionForView(itemView);
                            onItemDeleteListener.onItemDelete(position);
                            mPopupWindow.dismiss();
                        }
                    });
                    break;
            }
            // 如果有在滑動,消化事件
            return true;
        }
        return super.onTouchEvent(ev);
    }

    public void setOnItemDeleteListener(OnItemDeleteListener onItemDeleteListener) {
        this.onItemDeleteListener = onItemDeleteListener;
    }

    // 回調刪除事件的接口
    public interface OnItemDeleteListener {
        void onItemDelete(int position);
    }
}

②自定義消失和進入的動畫,因為系統中沒有我們想要的平移進入

style.xml:

<style name="PopupWindowAnimations" parent="@android:style/Animation">
    <item name="android:windowEnterAnimation">@anim/delete_enter</item>
    <item name="android:windowExitAnimation">@anim/delete_exit</item>
</style>

anim/delete_enter.xml:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:fromXDelta="200"
        android:toXDelta="0"
        android:fromYDelta="0"
        android:toYDelta="0"
        android:duration="200"/>
</set>

anim/delete_exit.xml:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="0"
        android:toXDelta="200"
        android:fromYDelta="0"
        android:toYDelta="0"
        android:duration="200"/>
</set>

③自定義Adapter,這裡不給出了,都懂

④在布局中使用自定義的ListView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <sixthweek.fndroid.com.sixthweek.SlidableListView
        android:id="@+id/slidableListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

⑤在Activity中使用adapter傳入數據即可

public class SlidableListViewActivity extends AppCompatActivity {
    private SlidableListView slidableListView;
    private MyAdapter adapter;
    private List<Map<String, String>> data;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_slidablelistview);
        slidableListView = (SlidableListView) findViewById(R.id.slidableListView);
        data = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            Map<String, String> map = new HashMap();
            map.put("name", "QQ" + i);
            data.add(map);
        }
        adapter = new MyAdapter(this, data);
        slidableListView.setAdapter(adapter);
        // 實現回調接口,處理邏輯
        slidableListView.setOnItemDeleteListener(new SlidableListView.OnItemDeleteListener() {
            @Override
            public void onItemDelete(int position) {
                data.remove(position);
                adapter.notifyDataSetChanged();
            }
        });
    }
}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved