編輯:關於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
Adapter類控件使用之DrawerLayout(官方側滑菜單)的簡單使用
(一)概述本節給大家帶來基礎UI控件部分的最後一個控件:DrawerLayout,官方給我們提供的一個側滑菜單控件,和上一節的ViewPager一樣,3.0以後引入,低版
Java的進化? Kotlin初探與集成Android項目
介紹:Statically typed programming language for the JVM, Android and the browser. 100% i
Unity3D之導出的Apk安裝失敗
最近用Unity3D導出Apk到手機上出現的問題,開始可以正常安裝到手機上。然而在我將導出的Apk在電腦的模擬機運行了幾次之後,再導入到手機上卻一直安裝失敗。後來在Pla
Android中的AppWidget入門教程
什麼是AppWidget?AppWidget就是我們平常在桌面上見到的那種一個個的小窗口,利用這個小窗口可以給用戶提供一些方便快捷的操作。本篇打算從以下幾個點來介紹App