Android自定義ListView實(shí)現(xiàn)仿QQ可拖拽列表功能
我們大致的思路,其實(shí)是這樣子的,也是我的設(shè)想,我們可以先去實(shí)現(xiàn)一個(gè)簡(jiǎn)單的ListView的數(shù)據(jù),但是他的Adapter,我們可以用系統(tǒng)封裝好的,然后傳遞進(jìn)去一個(gè)實(shí)體類,最后自定義一個(gè)listview去操作,所以我們先把準(zhǔn)備的工作做好,比如?
list_item.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/iv_logo" android:layout_width="50dp" android:layout_height="50dp" android:layout_alignParentLeft="true" android:layout_centerInParent="true" android:layout_marginLeft="10dp"/> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_marginLeft="10dp" android:layout_toRightOf="@id/iv_logo"/> </RelativeLayout>
這就只有一個(gè)頭像和一句話了,然后我們把實(shí)體類也給寫(xiě)完了
DragBean
package com.liuguilin.draglistviewsample.entity; /* * 項(xiàng)目名: DragListViewSample * 包名: com.liuguilin.draglistviewsample.entity * 文件名: DragBean * 創(chuàng)建者: LGL * 創(chuàng)建時(shí)間: 2016/8/29 22:49 * 描述: 實(shí)體類 */ 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,其實(shí)很簡(jiǎn)單,id是圖片,然后是文本,這樣我們就可以來(lái)實(shí)現(xiàn)一個(gè)Adapter了,這里我用的是ArrayAdapter這樣能讓我們插入和刪除很輕松
DragAdapter
package com.liuguilin.draglistviewsample.adapter; /* * 項(xiàng)目名: DragListViewSample * 包名: com.liuguilin.draglistviewsample.adapter * 文件名: DragAdapter * 創(chuàng)建者: LGL * 創(chuàng)建時(shí)間: 2016/8/29 22:41 * 描述: 拖拽列表的數(shù)據(jù)源 */ 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<DragBean> { /** * 構(gòu)造方法 * * @param context * @param mList */ public DragAdapter(Context context, List<DragBean> mList) { super(context, 0, mList); } /** * 實(shí)現(xiàn)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; } }
好的,其實(shí)到這里,他就是一個(gè)最普通的ListView了,我們給他填充點(diǎn)數(shù)據(jù)
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; //數(shù)據(jù) private List<DragBean> mList = new ArrayList<>(); //數(shù)據(jù)源 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); //新增數(shù)據(jù) for (int i = 0; i < 30; i++) { DragBean bean = new DragBean(R.mipmap.ic_launcher, "劉某人程序員" + i); mList.add(bean); } //初始化數(shù)據(jù)源 adapter = new DragAdapter(this,mList); mListView.setAdapter(adapter); } }
現(xiàn)在可以看看實(shí)際的效果了
現(xiàn)在我們可以重寫(xiě)我們的ListView了
我們首先攔截他的事件
/** * 獲取觸點(diǎn)所在條目的位置 * 獲取選中條目的圖片 * 事件的攔截機(jī)制 * * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { //識(shí)別動(dòng)作 switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //獲取觸點(diǎn)的坐標(biāo) int x = (int) ev.getX(); int y = (int) ev.getY(); //這樣就可以計(jì)算我按到哪個(gè)條目上了 mStartPosition = mEndPosition = pointToPosition(x, y); //判斷觸點(diǎn)是否在logo的區(qū)域 ViewGroup itemView = (ViewGroup) getChildAt(mStartPosition - getFirstVisiblePosition()); //記錄手指在條目中的相對(duì)Y坐標(biāo) dragPoint = y - itemView.getTop(); //ListView在屏幕中的Y坐標(biāo) dragOffset = (int) (ev.getRawY() - y); //拖動(dòng)的圖標(biāo) View dragger = itemView.findViewById(R.id.iv_logo); //判斷觸點(diǎn)是否在logo區(qū)域 if (dragger != null && x < dragger.getRight() + 10) { //定義ListView的滾動(dòng)條目 //上 upScroll = getHeight() / 3; //下 downScroll = getHeight() * 2 / 3; //獲取選中的圖片/截圖 itemView.setDrawingCacheEnabled(true); //獲取截圖 Bitmap bitMap = itemView.getDrawingCache(); //圖片滾動(dòng) startDrag(bitMap, y); } break; } //還會(huì)傳遞事件到子View return false; }
獲取到他的position之后我們直接截圖,并且顯示我們的window,這里做的事情就比較多了我們還要判斷是否點(diǎn)擊的是頭像才去顯示window
/** * 圖片在Y軸,也就是上下可滾動(dòng) * * @param bitMap * @param y */ private void startDrag(Bitmap bitMap, int y) { //窗體仿照 wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); //設(shè)置窗體參數(shù) lParams = new WindowManager.LayoutParams(); //設(shè)置寬高 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; //設(shè)置半透明 lParams.alpha = 0.8f; //設(shè)置居中 lParams.gravity = Gravity.TOP; //設(shè)置xy lParams.x = 0; lParams.y = y-dragPoint + dragOffset; //屬性 lParams.format = PixelFormat.TRANSLUCENT; //設(shè)置動(dòng)畫(huà) lParams.windowAnimations = 0; //圖片 dragImageView = new ImageView(getContext()); //設(shè)置截圖 dragImageView.setImageBitmap(bitMap); //添加顯示窗體 wm.addView(dragImageView, lParams); }
好的,我們初始化一下window,光顯示還不行呢,我們還要可以滑動(dòng),怎么監(jiān)聽(tīng)?onTouchEvent的ACTION_MOVE事件是可以做到的
/** * 觸摸事件 * * @param ev * @return */ @Override public boolean onTouchEvent(MotionEvent ev) { //錯(cuò)誤的位置 if (dragImageView != null && mEndPosition != INVALID_POSITION) { //在滑動(dòng)事件中控制上下滑動(dòng) switch (ev.getAction()) { case MotionEvent.ACTION_MOVE: //直接獲取到Y(jié)坐標(biāo)進(jìn)行移動(dòng) int moveY = (int) ev.getY(); doDrag(moveY); break; //停止拖動(dòng)成像 case MotionEvent.ACTION_UP: int upY = (int) ev.getY(); stopDrag(); onDrag(upY); break; } } //攔截到事件 return true; }
我們?cè)谝苿?dòng)的時(shí)候不斷的去更新他的位置
/** * 控制窗體移動(dòng) * * @param moveY */ private void doDrag(int moveY) { if (dragImageView != null) { lParams.y = moveY - dragPoint + dragOffset; wm.updateViewLayout(dragImageView, lParams); } //判斷移動(dòng)到分割線 返回-1 int tempLine = pointToPosition(0, moveY); //我們處理他 if (tempLine != INVALID_POSITION) { //只要你不移動(dòng)到分割線 我才處理 mEndPosition = tempLine; } //拖拽時(shí)滾動(dòng)、滾動(dòng)速度 int scrollSpeed = 0; //上滾 if (moveY < upScroll) { scrollSpeed = 10; //下滾 } else if (moveY > downScroll) { scrollSpeed = -10; } //開(kāi)始滾動(dòng) if (scrollSpeed != 0) { //計(jì)算條目的Y坐標(biāo) int dragItemY = getChildAt(mEndPosition - getFirstVisiblePosition()).getTop(); //當(dāng)前速度 int dy = dragItemY + scrollSpeed; //設(shè)置 setSelectionFromTop(mEndPosition, dy); } }
當(dāng)你移動(dòng)完成之后,我就可以停止你的window體了
/** * 停止的位置 */ private void stopDrag() { //直接移除窗體 if (dragImageView != null) { wm.removeView(dragImageView); dragImageView = null; } }
這樣,我就直接拼接你的position達(dá)到一個(gè)拖拽的效果
/** * 最終開(kāi)始成像 * * @param upY */ private void onDrag(int upY) { //分割線的處理 //判斷移動(dòng)到分割線 返回-1 int tempLine = pointToPosition(0, upY); //我們處理他 if (tempLine != INVALID_POSITION) { //只要你不移動(dòng)到分割線 我才處理 mEndPosition = tempLine; } /** * 你在最上方就直接落在第一個(gè)最下方就直接最下面一個(gè) */ //上邊界處理 if (upY < getChildAt(1).getTop()) { mEndPosition = 1; //下邊界處理 } else if (upY > getChildAt(getChildCount() - 1).getTop()) { mEndPosition = getAdapter().getCount() - 1; } //開(kāi)始更新item順序 if (mEndPosition > 0 && mEndPosition < getAdapter().getCount()) { DragAdapter adapter = (DragAdapter) getAdapter(); //刪除原來(lái)的條目 adapter.remove(adapter.getItem(mStartPosition)); //更新 adapter.insert(adapter.getItem(mStartPosition), mEndPosition); } }
全部代碼貼上
DragListView
package com.liuguilin.draglistviewsample.view; /* * 項(xiàng)目名: DragListViewSample * 包名: com.liuguilin.draglistviewsample.view * 文件名: DragListView * 創(chuàng)建者: LGL * 創(chuàng)建時(shí)間: 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; //需要達(dá)到的position private int mEndPosition; //手指在條目中的相對(duì)Y坐標(biāo) private int dragPoint; //ListView在屏幕中的Y坐標(biāo) private int dragOffset; //上 private int upScroll; //下 private int downScroll; //窗體 private WindowManager wm; //顯示的截圖 private ImageView dragImageView; //窗體參數(shù) private WindowManager.LayoutParams lParams; //構(gòu)造方法 public DragListView(Context context, AttributeSet attrs) { super(context, attrs); } /** * 獲取觸點(diǎn)所在條目的位置 * 獲取選中條目的圖片 * 事件的攔截機(jī)制 * * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { //識(shí)別動(dòng)作 switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //獲取觸點(diǎn)的坐標(biāo) int x = (int) ev.getX(); int y = (int) ev.getY(); //這樣就可以計(jì)算我按到哪個(gè)條目上了 mStartPosition = mEndPosition = pointToPosition(x, y); //判斷觸點(diǎn)是否在logo的區(qū)域 ViewGroup itemView = (ViewGroup) getChildAt(mStartPosition - getFirstVisiblePosition()); //記錄手指在條目中的相對(duì)Y坐標(biāo) dragPoint = y - itemView.getTop(); //ListView在屏幕中的Y坐標(biāo) dragOffset = (int) (ev.getRawY() - y); //拖動(dòng)的圖標(biāo) View dragger = itemView.findViewById(R.id.iv_logo); //判斷觸點(diǎn)是否在logo區(qū)域 if (dragger != null && x < dragger.getRight() + 10) { //定義ListView的滾動(dòng)條目 //上 upScroll = getHeight() / 3; //下 downScroll = getHeight() * 2 / 3; //獲取選中的圖片/截圖 itemView.setDrawingCacheEnabled(true); //獲取截圖 Bitmap bitMap = itemView.getDrawingCache(); //圖片滾動(dòng) startDrag(bitMap, y); } break; } //還會(huì)傳遞事件到子View return false; } /** * 圖片在Y軸,也就是上下可滾動(dòng) * * @param bitMap * @param y */ private void startDrag(Bitmap bitMap, int y) { //窗體仿照 wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); //設(shè)置窗體參數(shù) lParams = new WindowManager.LayoutParams(); //設(shè)置寬高 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; //設(shè)置半透明 lParams.alpha = 0.8f; //設(shè)置居中 lParams.gravity = Gravity.TOP; //設(shè)置xy lParams.x = 0; lParams.y = y-dragPoint + dragOffset; //屬性 lParams.format = PixelFormat.TRANSLUCENT; //設(shè)置動(dòng)畫(huà) lParams.windowAnimations = 0; //圖片 dragImageView = new ImageView(getContext()); //設(shè)置截圖 dragImageView.setImageBitmap(bitMap); //添加顯示窗體 wm.addView(dragImageView, lParams); } /** * 觸摸事件 * * @param ev * @return */ @Override public boolean onTouchEvent(MotionEvent ev) { //錯(cuò)誤的位置 if (dragImageView != null && mEndPosition != INVALID_POSITION) { //在滑動(dòng)事件中控制上下滑動(dòng) switch (ev.getAction()) { case MotionEvent.ACTION_MOVE: //直接獲取到Y(jié)坐標(biāo)進(jìn)行移動(dòng) int moveY = (int) ev.getY(); doDrag(moveY); break; //停止拖動(dòng)成像 case MotionEvent.ACTION_UP: int upY = (int) ev.getY(); stopDrag(); onDrag(upY); break; } } //攔截到事件 return true; } /** * 最終開(kāi)始成像 * * @param upY */ private void onDrag(int upY) { //分割線的處理 //判斷移動(dòng)到分割線 返回-1 int tempLine = pointToPosition(0, upY); //我們處理他 if (tempLine != INVALID_POSITION) { //只要你不移動(dòng)到分割線 我才處理 mEndPosition = tempLine; } /** * 你在最上方就直接落在第一個(gè)最下方就直接最下面一個(gè) */ //上邊界處理 if (upY < getChildAt(1).getTop()) { mEndPosition = 1; //下邊界處理 } else if (upY > getChildAt(getChildCount() - 1).getTop()) { mEndPosition = getAdapter().getCount() - 1; } //開(kāi)始更新item順序 if (mEndPosition > 0 && mEndPosition < getAdapter().getCount()) { DragAdapter adapter = (DragAdapter) getAdapter(); //刪除原來(lái)的條目 adapter.remove(adapter.getItem(mStartPosition)); //更新 adapter.insert(adapter.getItem(mStartPosition), mEndPosition); } } /** * 停止的位置 */ private void stopDrag() { //直接移除窗體 if (dragImageView != null) { wm.removeView(dragImageView); dragImageView = null; } } /** * 控制窗體移動(dòng) * * @param moveY */ private void doDrag(int moveY) { if (dragImageView != null) { lParams.y = moveY - dragPoint + dragOffset; wm.updateViewLayout(dragImageView, lParams); } //判斷移動(dòng)到分割線 返回-1 int tempLine = pointToPosition(0, moveY); //我們處理他 if (tempLine != INVALID_POSITION) { //只要你不移動(dòng)到分割線 我才處理 mEndPosition = tempLine; } //拖拽時(shí)滾動(dòng)、滾動(dòng)速度 int scrollSpeed = 0; //上滾 if (moveY < upScroll) { scrollSpeed = 10; //下滾 } else if (moveY > downScroll) { scrollSpeed = -10; } //開(kāi)始滾動(dòng) if (scrollSpeed != 0) { //計(jì)算條目的Y坐標(biāo) int dragItemY = getChildAt(mEndPosition - getFirstVisiblePosition()).getTop(); //當(dāng)前速度 int dy = dragItemY + scrollSpeed; //設(shè)置 setSelectionFromTop(mEndPosition, dy); } } }
然后我們引用
layout_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent"> <com.liuguilin.draglistviewsample.view.DragListView android:id="@+id/mListView" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout>
對(duì)了,別忘記了添加窗體的權(quán)限哦
<!--窗口權(quán)限--> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
做完這一系列事情之后,就覺(jué)得這個(gè)設(shè)想其實(shí)是對(duì)的,然后我們可以嘗試性的運(yùn)行
以上所述是小編給大家介紹的Android自定義ListView實(shí)現(xiàn)仿QQ可拖拽列表功能,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Android調(diào)試神器stetho使用詳解和改造
今天小編就為大家分享一篇關(guān)于Android調(diào)試神器stetho使用詳解和改造,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-02-02android截圖事件監(jiān)聽(tīng)的原理與實(shí)現(xiàn)
本篇文章主要介紹了android截圖事件監(jiān)聽(tīng)的原理與實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-07-07Android實(shí)現(xiàn)仿網(wǎng)易新聞的頂部導(dǎo)航指示器
這篇文章主要介紹了Android實(shí)現(xiàn)仿網(wǎng)易新聞的頂部導(dǎo)航指示器的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-08-08Android多線程學(xué)習(xí)實(shí)例詳解
這篇文章主要介紹了Android多線程,結(jié)合實(shí)例形式較為詳細(xì)的分析了Android多線程的概念、使用方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2016-10-10Flutter實(shí)現(xiàn)PopupMenu彈出式菜單按鈕詳解
這篇文章主要介紹了Flutter實(shí)現(xiàn)PopupMenu彈出式菜單按鈕,PopupMenuButton是一個(gè)用于創(chuàng)建彈出菜單的小部件,當(dāng)用戶點(diǎn)擊觸發(fā)按鈕時(shí),PopupMenuButton會(huì)在屏幕上方或下方彈出一個(gè)菜單,感興趣想要詳細(xì)了解可以參考下文2023-05-05Android kotlin使用注解實(shí)現(xiàn)防按鈕連點(diǎn)功能的示例
這篇文章主要介紹了Android kotlin使用注解實(shí)現(xiàn)防按鈕連點(diǎn)功能的示例,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-03-03自定義GridView并且實(shí)現(xiàn)拖拽(附源碼)
本文實(shí)現(xiàn)了GridView的拖拽功能,原理很簡(jiǎn)單只是在交換位置上記錄了X軸的相關(guān)坐標(biāo),計(jì)算了X軸的相關(guān)變量,實(shí)例代碼如下,感興趣的額朋友可以參考下哈2013-06-06android完美實(shí)現(xiàn) 拍照 選擇圖片 剪裁等代碼分享
本文給大家分享了2個(gè)安卓實(shí)現(xiàn)實(shí)現(xiàn) 拍照 選擇圖片 剪裁等的代碼,都是從正式項(xiàng)目中提取出來(lái)了,非常實(shí)用,有需要的小伙伴可以參考下。2016-01-01Android onbackpressed實(shí)現(xiàn)返回鍵的攔截和彈窗流程分析
很多網(wǎng)友不明白如何在Android平臺(tái)上捕獲Back鍵的事件,Back鍵是手機(jī)上的后退鍵,一般的軟件不捕獲相關(guān)信息可能導(dǎo)致你的程序被切換到后臺(tái),而回到桌面的尷尬情況,在Android上有兩種方法來(lái)獲取該按鈕的事件2023-01-01