Android ListView側滑item,仿QQ刪除效果
最近的專案需求有一條是要實現仿QQ的側滑刪除效果,網上搜到了很多,但是與預想的都不太一樣,於是自己研究了一下,寫了一個Demo,記錄下來。
功能:
1.實現了仿QQ的Item側滑效果
2.可根據item的長度計算側滑範圍
3.實現item條目點選監聽與刪除按鈕監聽
4.解決了刪除按鈕出現時ListView滑動錯位問題(偷懶:滑動時item復位)
主要程式碼:
SlidePauseListView:
繼承ListView,實現主要功能,dispatchTouchEvent進行事件分發,由自身的onTouchEvent方法處理側滑事件。其中package com.jcking.slidepauselistview.view; import android.content.Context; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.WindowManager; import android.widget.AdapterView; import android.widget.ListView; import android.widget.Scroller; public class SlidePauseListView extends ListView { /** * 當前滑動的ListView position */ private int slidePosition; /** * 手指按下X的座標 */ private int downY; /** * 手指按下Y的座標 */ private int downX; /** * 螢幕寬度 */ private int screenWidth; /** * ListView的item */ private View itemView; /** * 滑動類 */ private Scroller scroller; /** * 滑動速度邊界值 */ private static final int SNAP_VELOCITY = 600; /** * 速度追蹤物件 */ private VelocityTracker velocityTracker; /** * 是否響應滑動,預設為不響應 */ private boolean isSlide = false; /** * 認為是使用者滑動的最小距離 */ private int mTouchSlop; /** * 最多可滑動的範圍 */ private int mTouchLimit; /** * 是否已經側滑 */ private boolean hasSlided = false; public SlidePauseListView(Context context) { this(context, null); } public SlidePauseListView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SlidePauseListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); DisplayMetrics dm = new DisplayMetrics(); ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(dm); screenWidth = dm.widthPixels; scroller = new Scroller(context); mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); mTouchLimit = screenWidth / 4; } /** * 設定可滑動的最大值 * @param limit */ public void setTouchLimit(int limit){ this.mTouchLimit = limit; } /** * 獲取可滑動的最大值 * @return */ public int getTouchLimit(){ return this.mTouchLimit; } /** * 退回原位 */ public void slideBack(){ if(hasSlided && itemView != null){ scrollXBy(itemView, -mTouchLimit); hasSlided = false; } } /** * 分發事件,主要做的是判斷點選的是那個item, 以及通過postDelayed來設定響應左右滑動事件 */ @Override public boolean dispatchTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { addVelocityTracker(event); // 假如scroller滾動還沒有結束,我們直接返回 if (!scroller.isFinished()) { return super.dispatchTouchEvent(event); } // 如果已經側滑了,我們直接返回 if(hasSlided){ return super.dispatchTouchEvent(event); } downX = (int) event.getX(); downY = (int) event.getY(); slidePosition = pointToPosition(downX, downY); // 無效的position, 不做任何處理 if (slidePosition == AdapterView.INVALID_POSITION) { return super.dispatchTouchEvent(event); } // 獲取我們點選的item view itemView = getChildAt(slidePosition - getFirstVisiblePosition()); if(itemView instanceof SlideListItem){ SlideListItem item = (SlideListItem) itemView; int count = item.getChildCount(); mTouchLimit = 0; for (int i = 0; i < count; i++) { if(i > 0){ mTouchLimit += item.getChildAt(i).getMeasuredWidth(); } } } break; } case MotionEvent.ACTION_MOVE: { // Log.d("test", "downX : " + downX + ", event.getX() : " + event.getX() + ", mTouchSlop : " + mTouchSlop); if (Math.abs(getScrollVelocity()) > SNAP_VELOCITY || (downX - event.getX() > mTouchSlop && Math .abs(event.getY() - downY) < mTouchSlop)) { isSlide = true; } break; } case MotionEvent.ACTION_UP: recycleVelocityTracker(); break; } return super.dispatchTouchEvent(event); } /** * 處理我們拖動ListView item的邏輯 */ @Override public boolean onTouchEvent(MotionEvent ev) { Log.d("test", "hasSlided : " + hasSlided + ", isSlide : " + isSlide); // 如果已經側滑了,迴歸原位 if(hasSlided){ scrollXBy(itemView, -mTouchLimit); hasSlided = false; // 攔截事件,不繼續處理 return false; } if (isSlide && slidePosition != AdapterView.INVALID_POSITION) { requestDisallowInterceptTouchEvent(true); addVelocityTracker(ev); final int action = ev.getAction(); int x = (int) ev.getX(); switch (action) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL | (ev.getActionIndex()<< MotionEvent.ACTION_POINTER_INDEX_SHIFT)); onTouchEvent(cancelEvent); int deltaX = downX - x; downX = x; // 手指拖動itemView滾動, deltaX大於0向左滾動,小於0向右滾 scrollXBy(itemView, deltaX); return true; //拖動的時候ListView不滾動 case MotionEvent.ACTION_UP: int velocityX = getScrollVelocity(); if (velocityX < -SNAP_VELOCITY) { scrollLeft(); } else { scrollByDistanceX(); } recycleVelocityTracker(); // 手指離開的時候就不響應左右滾動 isSlide = false; break; } } // boolean result = super.onTouchEvent(ev); // Log.d("test", "result : " + result); //否則直接交給ListView來處理onTouchEvent事件 return super.onTouchEvent(ev); } /** * 讓指定的view滾動x位置,設定左右邊界,view最多滾動到邊界位置 * @param view * @param x */ private void scrollXBy(View view, int x){ // 如果已經滑動了最大值,並希望繼續向左滑,忽略 if(view.getScrollX() >= mTouchLimit && x >= 0) return; // 如果已經回到原位,並希望繼續想右劃,忽略 if(view.getScrollX() <= 0 && x <= 0) return; if(view.getScrollX() + x > mTouchLimit){ x = mTouchLimit - view.getScrollX(); }else if(view.getScrollX() + x < 0){ x = -view.getScrollX(); } view.scrollBy(x, 0); } @Override public void computeScroll() { // 呼叫startScroll的時候scroller.computeScrollOffset()返回true, if (scroller.computeScrollOffset()) { // 讓ListView item根據當前的滾動偏移量進行滾動 itemView.scrollTo(scroller.getCurrX(), scroller.getCurrY()); postInvalidate(); } } /** * 向左滑動,根據上面我們知道向左滑動為正值 */ private void scrollLeft() { final int delta = (mTouchLimit - itemView.getScrollX()); // 呼叫startScroll方法來設定一些滾動的引數,我們在computeScroll()方法中呼叫scrollTo來滾動item scroller.startScroll(itemView.getScrollX(), 0, delta, 0, Math.abs(delta)); postInvalidate(); // 重新整理itemView hasSlided = true; } /** * 根據手指滾動itemView的距離來判斷是滾動到開始位置還是向左或者向右滾動 */ private void scrollByDistanceX() { // 如果向左滾動的距離大於螢幕的二分之一,就讓其刪除 if (itemView.getScrollX() >= mTouchLimit / 2) { scrollLeft(); } else { // 滾回到原始位置,為了偷下懶這裡是直接呼叫scrollTo滾動 itemView.scrollTo(0, 0); } } /** * 新增使用者的速度跟蹤器 * * @param event */ private void addVelocityTracker(MotionEvent event) { if (velocityTracker == null) { velocityTracker = VelocityTracker.obtain(); } velocityTracker.addMovement(event); } /** * 移除使用者速度跟蹤器 */ private void recycleVelocityTracker() { if (velocityTracker != null) { velocityTracker.recycle(); velocityTracker = null; } } /** * 獲取X方向的滑動速度,大於0向右滑動,反之向左 * * @return */ private int getScrollVelocity() { velocityTracker.computeCurrentVelocity(1000); int velocity = (int) velocityTracker.getXVelocity(); return velocity; } }
通過計算item的長度,設定側滑的最大偏移量。ScrollXBy方法,防止側滑越界。if(itemView instanceof SlideListItem){ SlideListItem item = (SlideListItem) itemView; int count = item.getChildCount(); mTouchLimit = 0; for (int i = 0; i < count; i++) { if(i > 0){ mTouchLimit += item.getChildAt(i).getMeasuredWidth(); } } }
/** * 讓指定的view滾動x位置,設定左右邊界,view最多滾動到邊界位置 * @param view * @param x */ private void scrollXBy(View view, int x){ // 如果已經滑動了最大值,並希望繼續向左滑,忽略 if(view.getScrollX() >= mTouchLimit && x >= 0) return; // 如果已經回到原位,並希望繼續想右劃,忽略 if(view.getScrollX() <= 0 && x <= 0) return; if(view.getScrollX() + x > mTouchLimit){ x = mTouchLimit - view.getScrollX(); }else if(view.getScrollX() + x < 0){ x = -view.getScrollX(); } view.scrollBy(x, 0); }
SlideListItem:繼承LinearLayout,主要通過重寫onLayout方法使其中的子View不壓縮變形。
package com.jcking.slidepauselistview.view;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
public class SlideListItem extends LinearLayout {
public SlideListItem(Context context) {
this(context, null);
}
public SlideListItem(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int count = getChildCount();
for (int i = 0; i < count; i++) {
measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d("test", "event : " + event);
return super.dispatchTouchEvent(event);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int margeLeft = 0;
int size = getChildCount();
for (int i = 0; i < size; i++) {
View view = getChildAt(i);
if (view.getVisibility() != View.GONE) {
int childWidth = view.getMeasuredWidth();
// 將內部子孩子橫排排列
view.layout(margeLeft, 0, margeLeft + childWidth,
view.getMeasuredHeight());
margeLeft += childWidth;
}
}
}
}
SlideAdapter:在Adapter中繫結Click事件,分別監聽。
package com.jcking.slidepauselistview;
import java.util.List;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.TextView;
public class SlideAdapter extends BaseAdapter {
private List<String> mData;
private LayoutInflater mInflater;
private ViewHolder mHolder;
private OnSlideClickListener mListener;
class ViewHolder{
TextView tv;
View front;
Button btnClock;
Button btnDelete;
}
public SlideAdapter(Context context, List<String> data, OnSlideClickListener listener){
this.mData = data;
this.mInflater = LayoutInflater.from(context);
this.mListener = listener;
}
@Override
public int getCount() {
return mData == null ? 0 : mData.size();
}
@Override
public String getItem(int position) {
return mData == null ? null : mData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
convertView = mInflater.inflate(R.layout.item_listview, null);
mHolder = new ViewHolder();
mHolder.tv = (TextView) convertView.findViewById(R.id.tv);
mHolder.front = convertView.findViewById(R.id.front);
mHolder.btnClock = (Button) convertView.findViewById(R.id.btnClock);
mHolder.btnDelete = (Button) convertView.findViewById(R.id.btnDelete);
convertView.setTag(mHolder);
}else{
mHolder = (ViewHolder) convertView.getTag();
}
mHolder.tv.setText(getItem(position));
// 使隱藏的按鈕數量不一樣,測試可滑動範圍
mHolder.btnClock.setVisibility(position%2==0 ? View.GONE : View.VISIBLE);
if(mListener != null){
final int pos = position;
final View item = convertView;
mHolder.btnClock.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.onClockClick(pos, item);
}
});
mHolder.btnDelete.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.onDeleteClick(pos, item);
}
});
mHolder.front.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mListener.onItemClick(pos, item);
}
});
}
return convertView;
}
public interface OnSlideClickListener{
public void onItemClick(int position, View item);
public void onClockClick(int position, View item);
public void onDeleteClick(int position, View item);
}
}
MainActivity:簡單呼叫
package com.jcking.slidepauselistview;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import com.jcking.slidepauselistview.SlideAdapter.OnSlideClickListener;
import com.jcking.slidepauselistview.view.SlidePauseListView;
public class MainActivity extends Activity implements OnSlideClickListener {
private SlidePauseListView slideCutListView;
private SlideAdapter adapter;
private List<String> dataSourceList = new ArrayList<String>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
slideCutListView = (SlidePauseListView) findViewById(R.id.slideCutListView);
for (int i = 0; i < 20; i++) {
dataSourceList.add("滑動刪除" + i);
}
adapter = new SlideAdapter(this, dataSourceList, this);
slideCutListView.setAdapter(adapter);
}
@Override
public void onItemClick(int position, View item) {
// TODO Auto-generated method stub
Toast.makeText(this, "點選條目 " + position, Toast.LENGTH_SHORT).show();
}
@Override
public void onClockClick(int position, View item) {
// TODO Auto-generated method stub
Toast.makeText(this, "點選鬧鐘 " + position, Toast.LENGTH_SHORT) .show();
}
@Override
public void onDeleteClick(int position, View item) {
// TODO Auto-generated method stub
Toast.makeText(this, "點選刪除 " + position, Toast.LENGTH_SHORT) .show();
slideCutListView.slideBack();
dataSourceList.remove(position);
adapter.notifyDataSetChanged();
}
}
activity_main:主檢視佈局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.jcking.slidepauselistview.view.SlidePauseListView
android:id="@+id/slideCutListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:cacheColorHint="@android:color/transparent"
android:divider="#2b2b2b"
android:dividerHeight="0.5dp"
android:listSelector="@android:color/transparent" >
</com.jcking.slidepauselistview.view.SlidePauseListView>
</LinearLayout>
item_listview:item佈局
<?xml version="1.0" encoding="UTF-8"?>
<com.jcking.slidepauselistview.view.SlideListItem xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#acacac"
android:orientation="horizontal" >
<!-- 這裡一定要設定android:layout_width屬性為充滿螢幕 -->
<LinearLayout
android:id="@+id/front"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="5dp" >
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="測試資料"
android:textSize="18sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal" >
<Button
android:id="@+id/btnClock"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#ff0000"
android:text="鬧鈴"
android:textColor="#ffffff" />
<Button
android:id="@+id/btnDelete"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#00ffff"
android:text="刪除"
android:textColor="#ffffff" />
</LinearLayout>
</com.jcking.slidepauselistview.view.SlideListItem>
提示:
1.item佈局中,第一個一定要充滿螢幕
問題:
1.靈敏度不高,不能像QQ一樣完全精確識別手勢
相關推薦
Android ListView側滑item,仿QQ刪除效果
最近的專案需求有一條是要實現仿QQ的側滑刪除效果,網上搜到了很多,但是與預想的都不太一樣,於是自己研究了一下,寫了一個Demo,記錄下來。 功能: 1.實現了仿QQ的Item側滑效果 2.可根據item的長度計算側滑範圍 3.實現item條目點選監聽與刪除按鈕監聽 4.
Android ListView側滑刪除
前言 今天是個值得紀念的日子,今天點亮了部落格專家!相信我是第一個粉絲數量在20位以下,排名在萬級別的以外的部落格專家。 一、使用 效果圖: maven: <depende
android listView點選item,高亮顯示
實現listView中選擇item高亮的功能,最重要的程式碼就是 adapter.notifyDataSetInvalidated(); 通知觀察者,此條資料不再有效。 其次需要在實現BaseAdapter的類中新增一個方法,紀錄選中的item的id
Android ListView 側滑效果實現(滑動展開、滑動刪除)
轉載請註明出處:http://blog.csdn.net/lonelyroamer/article/details/42439875 專案需要ListView滑動刪除的效果,首先肯定是拿來主義,在網上搜了一遍,發現這樣的東西真不少,比較有名的Github上的SwipeLi
【Android】自定義控制元件-仿QQ聯絡人側滑條目,右側滑選單。
一直沒有寫部落格的習慣,一直都是看別人的部落格,學習別人的東西。平時工作中總會遇到或大或小的問題,往往是上百度CSDN查詢答案。今天嘗試著寫部落格,一是更加深入地熟悉一下部落格;二是轉變一下學習方式;三是把自己所學的東西分享出來,幫助別人的同時也提升了自己!
Android滑動選單特效實現,仿人人客戶端側滑效果,史上最簡單的側滑實現
人人客戶端有一個特效還是挺吸引人的,在主介面手指向右滑動,就可以將選單展示出來,而主介面會被隱藏大部分,但是仍有左側的一小部分同選單一起展示。據說人人客戶端的這個特效是從facebook客戶端模仿來的,至於facebook是不是又從其它地方模仿來的就不得而知了。好,今天我們就一
Android滑動選單特效實現,仿人人客戶端側滑效果
人人客戶端有一個特效還是挺吸引人的,在主介面手指向右滑動,就可以將選單展示出來,而主介面會被隱藏大部分,但是仍有左側的一小部分同選單一起展示。 據說人人客戶端的這個特效是從facebook客戶端模仿來的,至於facebook是不是又從其它地方模仿來的就不得而
Android自定義控制元件----繼承ViewGroup側滑選單3,普通側滑選單,新增選單切換按鈕(完結)
專案結構: 思路: 對外暴露一個選單開啟和關閉選單的方法toggle 當開啟時this.smoothScrollTo(0, 0);滑動選單顯示 當關閉時this.smoothScrollTo(mMenuWidth, 0);選單隱藏
【Android】側滑選單欄功能的實現(類似於QQ)
通過NavigationView+DrawerLayout來實現側滑選單的功能(效果圖如上),可通過點選左上角圖示以及向右滑動實現側滑選單 首先在主頁佈局xml中使用DreawerLayout作為外包裝,將側滑選單包裝起來即可,而功能則使用navigat
android官方側滑菜單DrawerLayout詳解
lns 不同 earch 復雜 splay 第三方控件 ado sent fragment DrawerLayout是Support Library包中實現了側滑菜單效果的控件,可以說drawerLayout是因為第三方控件如MenuDrawer等的出現之後,google借
Android實現側滑-新增資料和toolbar等功能
首先是主程式碼塊 package alice.bw.com.day01zhoukao; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.Fra
Android ListView中的item只能有一個選中的問題和ListView與activity互動的問題
Android中ListView中的item與activity的互動有幾種方式:可以用回撥,廣播等,下面的方式是動態廣播的方式 ListView中的item選中事件,比如有多個item,每個item中都有一個CheckBox,我們要只選擇其中的一個,這是我們需要給每個item設定一
Android ListView不同的item佈局實現
類似與這個 聊天效果 和 進入直播間 注意 在使用不同佈局的時候,在介面卡中 getItemViewType和getViewTypeCount不能少,一般的同一種item的列表是不用這兩個函式的過載的,但是如果需要不同種類的item,就必須過載這兩個
android官方側滑選單DrawerLayout詳解
轉載地址:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/0925/1713.html drawerLayout是Support Library包中實現了側滑選單效果的控制元件,可以說drawerLayout是因為
安卓-ListView側滑(二)之SwipeMenuListView新增menu.getViewType()屬性控制是否側滑
這篇部落格記錄的是曾經做過的一個專案,需求是一個list裡面要求有的部分可以側滑刪除,有的部分不可以,側滑用的是開源專案SwipeMenuListView在網上查了些資料,根據這個需求又自己做了調整,終於完美的解決了,好,上程式碼。 1.在gradle中新增depend
安卓-ListView側滑刪除
前段時間用到了側滑刪除效果,在網上找了一些資料,結合一些程式碼做了個小測試,先看效果圖 效果圖
Android : tablayout +側滑選單 + 豎立的tablayout +資料庫增刪改查+XListView+PullToRefresh 綜合
側拉選單 一個XListview載入,一個是PullToRefresh重新整理 一個是本地資料的重新整理一個是網路資料的重新整理 資料庫的增刪查 需要用到的許可權 <uses-permission android:name="android.permission.I
SwipeRecyclerView 的側滑實現,simple版,有引入包的過程
效果圖: 左滑,出現新增 部落格內容 導包步驟 易出錯地方 完整程式碼 0. 導包步驟 Gradle implementation 'com.yanzhenjie:recyclerview-swipe:1.1.4' 在xml中引用SwipeRecycl
Android ListView左滑刪除、左滑自定義功能
https://www.cnblogs.com/guop/p/5066062.html 最近專案需要ListView左滑刪除功能,蒐集了很多資料發現了一個某一前輩寫的庫能很簡單的實現這個功能,而且有原始碼,直接拿來使用了。 庫名字叫做SwipeMenuListView,下面給大家演示一下使用
Android靜默安裝實現方案,仿360手機助手秒裝和智慧安裝功能
之前有很多朋友都問過我,在Android系統中怎樣才能實現靜默安裝呢?所謂的靜默安裝,就是不用彈出系統的安裝介面,在不影響使用者任何操作的情況下不知不覺地將程式裝好。雖說這種方式看上去不打攪使用者,但是卻存在著一個問題,因為Android系統會在安裝介面當中把程式所宣告的許可