編輯:關於Android編程
有時時候需要對ListView的Item進行手動拖拽排序,如安桌系統中的對通知欄的開關排序,因此需要自定義一個可拖拽的ListView,效果如下:
可見,該ListView只有已添加欄可以拖動,且拖動到頂部或底部時,會自動滾動列表。實現的原理為:
1.當點擊列表時,獲取點擊的itemView:
int position = pointToPosition(x, y); View itemView = getChildAt(position - getFirstVisiblePosition());
itemView.setDrawingCacheEnabled(true); // 開啟cache. mBitmap = Bitmap.createBitmap(itemView.getDrawingCache()); // 根據cache創建一個新的bitmap對象. itemView.setDrawingCacheEnabled(false);
3.根據拖拽的位置繪制cache:
protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); // 繪制拖拽的itemView,mLastY為觸摸點的y坐標,mDragViewOffset為觸摸點在itemView中的坐標y if (mBitmap != null && !mBitmap.isRecycled()) { canvas.drawBitmap(mBitmap, 0, mLastY - mDragViewOffset, null); } }
4.拖拽的過程中,判斷是否交換位置:
int position = pointToPosition(0, y); // 當前拖拽到的位置 if (position != mLastPosition) { // 位置發生變化 // onExchagne(mLastPosition, position), 交換adapter中的數據集 mLastPosition = position; }
public class DragListView extends ListView { private int mLastPosition; // 上次的位置,用於判斷是否跟當前交換位置 private int mCurrentPosition; // 手指點擊准備拖動的時候,當前拖動項在列表中的位置. private int mAutoScrollUpY; // 拖動的時候,開始向上滾動的邊界 private int mAutoScrollDownY; // 拖動的時候,開始向下滾動的邊界 private int mLastY; private int mDragViewOffset; // 觸摸點在itemView中的高度 private DragItemListener mDragItemListener; private boolean mHasStart = false; private Bitmap mBitmap; // 拖拽的itemView圖像 private View mItemView; private boolean mIsHideItemView = true; // 拖拽時是否隱藏itemView public DragListView(Context context) { this(context, null); } public DragListView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DragListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } /** * 觸摸事件處理 */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_OUTSIDE: case MotionEvent.ACTION_CANCEL: if (mBitmap != null) { stopDrag(); invalidate(); return true; } break; case MotionEvent.ACTION_MOVE: if (mBitmap != null) { if (!mHasStart) { mDragItemListener.startDrag(mCurrentPosition); mHasStart = true; } int moveY = (int) ev.getY(); if (moveY < 0) { // 限制觸摸范圍在ListView中 moveY = 0; } else if (moveY > getHeight()) { moveY = getHeight(); } onMove(moveY); mLastY = moveY; invalidate(); return true; } break; case MotionEvent.ACTION_DOWN: // 判斷是否進拖拽 stopDrag(); int x = (int) ev.getX(); // 獲取相對與ListView的x坐標 int y = (int) ev.getY(); // 獲取相應與ListView的y坐標 int temp = pointToPosition(x, y); if (temp == AdapterView.INVALID_POSITION) { // 無效不進行處理 return super.dispatchTouchEvent(ev); } mLastPosition = mCurrentPosition = temp; // 獲取當前位置的視圖(可見狀態) ViewGroup itemView = (ViewGroup) getChildAt(mCurrentPosition - getFirstVisiblePosition()); if (itemView != null && mDragItemListener != null && mDragItemListener.canDrag(itemView, x, y)) { // 觸摸點在item項中的高度 mDragViewOffset = y - itemView.getTop(); setLayerType(View.LAYER_TYPE_SOFTWARE, null); // 關閉硬件加速 mDragItemListener.beforeDrawingCache(itemView); itemView.setDrawingCacheEnabled(true); // 開啟cache. mBitmap = Bitmap.createBitmap(itemView.getDrawingCache()); // 根據cache創建一個新的bitmap對象. itemView.setDrawingCacheEnabled(false); mDragItemListener.afterDrawingCache(itemView); mHasStart = false; mLastY = y; if (mIsHideItemView) { // 隱藏itemView hideItemView(); } invalidate(); return true; } break; } return super.dispatchTouchEvent(ev); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); // 繪制拖拽的itemView if (mBitmap != null && !mBitmap.isRecycled()) { canvas.drawBitmap(mBitmap, 0, mLastY - mDragViewOffset, null); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mAutoScrollUpY = w / 4; // 取得向上滾動的邊際,大概為該控件的1/3 mAutoScrollDownY = h * 3 / 4; // 取得向下滾動的邊際,大概為該控件的2/3 } /** * 拖動執行,在Move方法中執行 * * @param y */ public void onMove(int y) { // 為了避免滑動到分割線的時候,返回-1的問題 int tempPosition = pointToPosition(0, y); if (tempPosition != INVALID_POSITION) { mCurrentPosition = tempPosition; } if (y < getChildAt(0).getTop()) { // 超出邊界處理(如果向上超過第二項Top的話,那麼就放置在第一個位置) mCurrentPosition = 0; } else if (y > getChildAt(getChildCount() - 1).getBottom()) { // // 如果拖動超過最後一項的最下邊那麼就防止在最下邊 mCurrentPosition = getAdapter().getCount() - 1; } checkExchange(y); // 時時交換 checkScroller(y); // listview移動. } /*** * ListView的移動. * 要明白移動原理:當我移動到下端的時候,ListView向上滑動,當我移動到上端的時候,ListView要向下滑動。正好和實際的相反. */ public void checkScroller(int y) { int offset = 0; if (y < mAutoScrollUpY) { // ListView需要下滑 if (y < mLastY) { offset = (mAutoScrollUpY - y) / 5; // 時時步伐 } } else if (y > mAutoScrollDownY) { // ListView需要上滑 if (y > mLastY) { offset = (mAutoScrollDownY - y) / 5; // 時時步伐 } } if (offset != 0) { View view = getChildAt(mCurrentPosition - getFirstVisiblePosition()); if (view != null) { setSelectionFromTop(mCurrentPosition, view.getTop() + offset); } } } /** * 停止拖動,刪除影像 */ public void stopDrag() { if (mBitmap != null) { mBitmap.recycle(); mBitmap = null; if (mDragItemListener != null) { mDragItemListener.onRelease(mCurrentPosition); } } if (mItemView != null) { mItemView.setVisibility(View.VISIBLE); mItemView = null; } } /*** * 拖動時時change */ private void checkExchange(int y) { // 數據交換 if (mCurrentPosition != mLastPosition) { if (mDragItemListener != null) { if (mDragItemListener.onExchange(mLastPosition, mCurrentPosition)) { // 進行數據交換,true則表示交換成功 mLastPosition = mCurrentPosition; if (mIsHideItemView) { // 隱藏實際的itemView hideItemView(); } } } } } // 隱藏實際的itemView private void hideItemView() { if (mItemView != null) { mItemView.setVisibility(View.VISIBLE); } mItemView = getChildAt(mCurrentPosition - getFirstVisiblePosition()); // 隱藏實際的itemView if (mItemView != null) { mItemView.setVisibility(View.INVISIBLE); } } public void setDragItemListener(DragItemListener listener) { mDragItemListener = listener; } public DragItemListener getDragListener() { return mDragItemListener; } /** * 拖拽監聽器 */ public interface DragItemListener { /** * 數據交換 * * @param srcPosition * @param position * @return 返回true,則確認數據交換;返回false則表示放棄 */ boolean onExchange(int srcPosition, int position); /** * 釋放手指 * * @param position */ void onRelease(int position); /** * 是否可以拖拽 * * @param itemView * @param x 當前觸摸的坐標 * @param y * @return */ boolean canDrag(View itemView, int x, int y); /** * 開始拖拽 * * @param position */ void startDrag(int position); /** * 在生成拖影(itemView.getDrawingCache())之前 * * @param itemView */ void beforeDrawingCache(View itemView); /** * 在生成拖影(itemView.getDrawingCache())之後 * * @param itemView */ void afterDrawingCache(View itemView); } }
mListView.setDragItemListener(new DragListView.DragItemListener() { private Rect mFrame = new Rect(); private boolean mIsSelected; @Override public boolean onExchange(int srcPosition, int position) { boolean result = mAdapter.exchange(srcPosition, position); return result; } @Override public void onRelease(int positon) { } @Override public boolean canDrag(View dragView, int x, int y) { // 獲取可拖拽的圖標 View dragger = dragView.findViewById(R.id.dl_plugin_move); if (dragger == null || dragger.getVisibility() != View.VISIBLE) { return false; } float tx = x - ViewUtil.getX(dragView); float ty = y - ViewUtil.getY(dragView); dragger.getHitRect(mFrame); if (mFrame.contains((int) tx, (int) ty)) { // 當點擊拖拽圖標才可進行拖拽 return true; } return false; } @Override public void startDrag(int position) { } @Override public void beforeDrawingCache(View dragView) { mIsSelected = dragView.isSelected(); View drag = dragView.findViewById(R.id.dl_plugin_move); dragView.setSelected(true); if (drag != null) { drag.setSelected(true); } } @Override public void afterDrawingCache(View dragView) { dragView.setSelected(mIsSelected); View drag = dragView.findViewById(R.id.dl_plugin_move); if (drag != null) { drag.setSelected(false); } } });
實踐中會不斷的改進的代碼,請大家關注最新完整的代碼:Androids" target="_blank">https://github.com/1993hzw/Androids
(一)概述本節給大家帶來基礎UI控件部分的最後一個控件:DrawerLayout,官方給我們提供的一個側滑菜單控件,和上一節的ViewPager一樣,3.0以後引入,低版
介紹:Statically typed programming language for the JVM, Android and the browser. 100% i
最近用Unity3D導出Apk到手機上出現的問題,開始可以正常安裝到手機上。然而在我將導出的Apk在電腦的模擬機運行了幾次之後,再導入到手機上卻一直安裝失敗。後來在Pla
什麼是AppWidget?AppWidget就是我們平常在桌面上見到的那種一個個的小窗口,利用這個小窗口可以給用戶提供一些方便快捷的操作。本篇打算從以下幾個點來介紹App