本文主要是介绍一分钟轻松打造左滑删除+下拉刷新Listview控件,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
尊重原创转载请注明出处:http://blog.csdn.net/guitarstudio/article/details/48525339 Power by guitar 侵权必究!
2017年8月21号更新内容:
最新代码已经上传到github,博客中源码解析中的代码不是最新的
github地址:https://github.com/guitarstar/SwipeLayout
如果仅仅关注如果使用,可以看如下直接使用的方法:
步骤 1. Add the JitPack repository to your build fileallprojects {repositories {maven { url "https://jitpack.io" }}
}
步骤 2. Add the dependencydependencies {compile 'com.github.guitarstar:SwipeLayout:v1.0'
}
步骤 3. 列表的布局layout编写<com.solo.library.SlideTouchViewandroid:id="@+id/mSlideTouchView"android:layout_width="match_parent"android:layout_height="60dp"><!-- 下层布局 --><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="match_parent"android:orientation="horizontal"><Buttonandroid:id="@+id/btn_del"android:layout_width="wrap_content"android:layout_height="match_parent"android:background="@android:color/holo_red_light"android:text="删除"/></LinearLayout><!-- 上层布局 --><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:background="#fff"><!-- 这里设个背景颜色将下层布局遮掩 --><TextViewandroid:id="@+id/tv"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"/></LinearLayout>
</com.solo.library.SlideTouchView>
步骤 4. ListView的adapter继承SlideBaseAdapter(使用recyclerview的adapter继承SlideRecyclerViewBaseAdapter)如果使用ListView的情况:public class LvAdapter extends SlideBaseAdapter { List list; public LvAdapter(List list) { this.list = list; }@Override
public int[] getBindOnClickViewsIds() {return new int[]{R.id.btn_del}; //必须调用, 删除按钮或者其他你想监听点击事件的View的id
}@Override
public int getCount() {return list.size();
}@Override
public Object getItem(int position) {return list.get(position);
}@Override
public long getItemId(int position) {return 0;
}@Override
public View getView(int position, View convertView, ViewGroup parent) {MyViewHolder holder = new MyViewHolder();if (convertView == null) {convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_list_item, null);holder.tv = (TextView) convertView.findViewById(tv);holder.mSlideTouchView = (SlideTouchView) convertView.findViewById(R.id.mSlideTouchView);convertView.setTag(holder);bindSlideState(holder.mSlideTouchView); //必须调用} else {holder = (MyViewHolder) convertView.getTag();}bindSlidePosition(holder.mSlideTouchView, position);//必须调用holder.tv.setText(String.valueOf(list.get(position)));return convertView;
}如果使用recyclerview的情况:
public class RcyAdapter extends SlideRecyclerViewBaseAdapter {List<Integer> list;public RcyAdapter(List<Integer> list) {this.list = list;}@Overridepublic int[] getBindOnClickViewsIds() {return new int[]{R.id.btn_del}; //必须调用, 删除按钮或者其他你想监听点击事件的View的id}@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_list_item, null);return new MyViewHolder(v);}@Overridepublic void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {MyViewHolder viewHolder = (MyViewHolder) holder;viewHolder.tv.setText(String.valueOf(list.get(position)));bindSlidePosition(viewHolder.mSlideTouchView, position);//必须调用}@Overridepublic int getItemCount() {return list.size();}class MyViewHolder extends RecyclerView.ViewHolder {TextView tv;SlideTouchView mSlideTouchView;public MyViewHolder(View itemView) {super(itemView);tv = (TextView) itemView.findViewById(R.id.tv);mSlideTouchView = (SlideTouchView) itemView.findViewById(R.id.mSlideTouchView);bindSlideState(mSlideTouchView);//必须调用}}
}
步骤 5. 点击事件监听 adapter.setupRecyclerView(mRecyclerView); //里面的逻辑是监听滚动关闭按钮显示 (ListView中调用adapter.setupListView(mListView);) adapter.setOnClickSlideItemListener(new OnClickSlideItemListener() { @Override public void onItemClick(ISlide iSlideView, View v, int position) { //点击item时会回调此方法(onClick中也会回调) Toast.makeText(v.getContext(), "click item position:" + position, Toast.LENGTH_SHORT).show(); }@Overridepublic void onClick(ISlide iSlideView, View v, int position) {//控件的所有子控件的点击回调都会回调此方法if (v.getId() == R.id.btn_del) { //对删除按钮的监听(上面adapter的getBindOnClickViewsIds()中设置了R.id.btn_del)iSlideView.close(); //关闭当前的按钮list.remove(position);adapter.notifyDataSetChanged();}}
});
如下是源码解析内容:
左滑删除最早应该是在iOS中出现的一个控件,后来相应地有了不少的android模仿者,其中一个就是我们伟大的android版腾讯QQ啦,它和iOS版的显示效果又有点不同,具体不同看下图:
大家可以用你的android手机和你的iPhone 6s打开手机QQ对比一下效果~~ 什么?没有iPhone 6s %¥……*……@% 没关系,有肾就好,哈哈哈
先看看最终实现的效果:
下面我们开始考虑怎么来实现。
首先,我们有两个点需要思考的:
- 我们是通过继承listview来实现呢,还是在listview加载相应的左滑删除布局。
- 怎么样来拖动这些布局。
我是这样考虑的:
- github上下拉刷新的轮子多得数不清,如果用继承listview的话不好整合。
- 可以用ViewDragHelper来实现。
好了,我们开始正式coding。
第一步:实现一个可以左右拖动的自定义View
package com.solo.charge.view;import android.content.Context;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;/*** Created by ling on 2015/9/9.*/
public class DragView extends RelativeLayout implements View.OnClickListener{private View fgView , bgView;private ViewDragHelper mDrager;private DragStateListener mDragStateListener;private final int DRAG_LEFT = -1 , DRAG_RIGHT = 1;private int dragMode = DRAG_LEFT;private float minX , maxX;public DragView(Context context) {super(context);}public DragView(Context context, AttributeSet attrs) {super(context, attrs);mDrager = ViewDragHelper.create(this, 5f, new ViewDragHelper.Callback() {@Overridepublic boolean tryCaptureView(View child, int pointerId) {return child == fgView;}@Overridepublic int getViewHorizontalDragRange(View child) {return bgView.getMeasuredWidth();}@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {return getPositionX(left);}@Overridepublic int clampViewPositionVertical(View child, int top, int dy) {return 0;}@Overridepublic void onViewReleased(View releasedChild, float xvel, float yvel) {super.onViewReleased(releasedChild, xvel, yvel);if(Math.abs(fgView.getLeft()) != 0 || Math.abs(fgView.getLeft()) != bgView.getMeasuredWidth()){float x = fgView.getLeft() + 0.1f * xvel;mDrager.smoothSlideViewTo(fgView,Math.abs(getPositionX(x)) > bgView.getMeasuredWidth() / 2 ? bgView.getMeasuredWidth() * dragMode : 0, 0);postInvalidate();}}@Overridepublic void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {super.onViewPositionChanged(changedView, left, top, dx, dy);if(changedView == fgView)getParent().requestDisallowInterceptTouchEvent(fgView.getLeft() != 0 ? true : false);if(mDragStateListener != null){if(left == 0){mDragStateListener.onClosed(DragView.this);}else if(Math.abs(left) == bgView.getMeasuredWidth()){mDragStateListener.onOpened(DragView.this);}}}});}public View getForegroundView() {return fgView;}public View getBackgroundView() {return bgView;}public void open(){fgView.offsetLeftAndRight(dragMode * (bgView.getMeasuredWidth() - fgView.getLeft()));}public void close(){fgView.offsetLeftAndRight(-fgView.getLeft());}public void openAnim(){mDrager.smoothSlideViewTo(fgView, bgView.getMeasuredWidth() * dragMode, 0);postInvalidate();}public void closeAnim(){mDrager.smoothSlideViewTo(fgView, 0, 0);postInvalidate();}public boolean isOpen(){return Math.abs(fgView.getLeft()) == bgView.getMeasuredWidth();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);//drag rangeif(dragMode == DRAG_LEFT){minX = -bgView.getMeasuredWidth();maxX = 0;}else{minX = 0;maxX = bgView.getMeasuredWidth();}}private int getPositionX(float x){if(x < minX) x = minX;if(x > maxX) x = maxX;return (int) x;}@Overrideprotected void onFinishInflate() {super.onFinishInflate();if(getChildCount() != 2)throw new IllegalArgumentException("must contain only two child view");fgView = getChildAt(1);bgView = getChildAt(0);if(!(fgView instanceof ViewGroup && bgView instanceof ViewGroup))throw new IllegalArgumentException("ForegroundView and BackgoundView must be a subClass of ViewGroup");RelativeLayout.LayoutParams param = (RelativeLayout.LayoutParams)bgView.getLayoutParams();param.addRule(dragMode == DRAG_LEFT ? RelativeLayout.ALIGN_PARENT_RIGHT : RelativeLayout.ALIGN_PARENT_LEFT);param.width = LayoutParams.WRAP_CONTENT;//bind onClick EventfgView.setOnClickListener(this);int bgViewCount = ((ViewGroup)bgView).getChildCount();for (int i = 0; i < bgViewCount; i++) {View child = ((ViewGroup) bgView).getChildAt(i);if(child.isClickable()) child.setOnClickListener(this);}}@Overridepublic void computeScroll() {if(mDrager.continueSettling(true)){postInvalidate();}}public void setOnDragStateListener(DragStateListener listener){mDragStateListener = listener;}@Overridepublic void onClick(View v) {if(mDragStateListener != null) {if(v == fgView){if(isOpen()){closeAnim();return;}mDragStateListener.onForegroundViewClick(DragView.this , v);}else {mDragStateListener.onBackgroundViewClick(DragView.this , v);}}}public interface DragStateListener{void onOpened(DragView dragView);void onClosed(DragView dragView);void onForegroundViewClick(DragView dragView, View v);void onBackgroundViewClick(DragView dragView, View v);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {return mDrager.shouldInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {mDrager.processTouchEvent(event);return true;}
}
为了方便对这种上下层的关系来进行布局免去自己计算处理的麻烦,DragView选择继承RelativeLayout来实现。
131~132行,分别取出上层布局fgView和下层布局bgView,然后就是使用ViewDragHelper(没有使用过可以百度一下其用法,用起来还是很方便的)来对fgView控制拖动。
140~145行,处理点击事件,上层布局fgView的点击事件来代替listview中的OnItemClickListener;而下层布局bgView往往不仅仅只有一个删除按钮,也有可能有多个按钮,还需要分别对这些按钮来进行响应。
66行,在拖动的过程中不允许listview对其触摸事件拦截,防止拖动时和listview滚动发生冲突。
第二步,我们就可以开始用上面写好的DragView了,下面是ListView的Item的布局:
<?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"><com.solo.charge.view.DragView
android:id="@+id/drag_view"android:layout_width="match_parent"android:layout_height="60dp"><!-- 下层布局 --><LinearLayout
android:layout_width="wrap_content"android:layout_height="match_parent"android:orientation="horizontal"><Button
android:id="@+id/btn_del"android:layout_width="wrap_content"android:layout_height="match_parent"android:background="@android:color/holo_red_light"android:text="删除"/></LinearLayout><!-- 上层布局 --><LinearLayout
android:layout_width="match_parent"android:layout_height="match_parent"android:background="#fff"><!-- 这里设个背景颜色将下层布局遮掩 --><TextView
android:id="@+id/tv"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"/></LinearLayout></com.solo.charge.view.DragView>
</LinearLayout>
第三步,在Acitivity中使用ListView来展现它,但这里我们还要给ListView加下拉刷新功能,这里我们直接用轮子(https://github.com/chrisbanes/Android-PullToRefresh),为了方便测试,adaper也在Acitivity中来实现:
package com.solo.charge;import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;import com.handmark.pulltorefresh.library.PullToRefreshBase;
import com.handmark.pulltorefresh.library.PullToRefreshListView;
import com.solo.charge.view.DragView;import java.util.ArrayList;
import java.util.List;public class TestActivity extends ActionBarActivity {private PullToRefreshListView mPullToRefreshListView;private List<String> list = new ArrayList<String>();private MyAdapter adapter = new MyAdapter();private Handler mHandler = new Handler();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_test);mPullToRefreshListView = (PullToRefreshListView) findViewById(R.id.listview);for (int i = 0 ; i < 20 ; i++){list.add(String.valueOf(i));}mPullToRefreshListView.setAdapter(adapter);mPullToRefreshListView.setOnScrollListener(new AbsListView.OnScrollListener() {@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {adapter.close();}});mPullToRefreshListView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener<ListView>() {@Overridepublic void onRefresh(PullToRefreshBase<ListView> refreshView) {//模拟一下刷新数据,额mHandler.postDelayed(new Runnable() {@Overridepublic void run() {list.clear();for (int i = 0; i < 20; i++) {list.add(String.valueOf(i));}adapter.notifyDataSetChanged();mPullToRefreshListView.onRefreshComplete();}}, 1000);}});}class MyAdapter extends BaseAdapter{List<DragView> views = new ArrayList<DragView>();@Overridepublic int getCount() {return list.size();}@Overridepublic Object getItem(int position) {return list.get(position);}@Overridepublic long getItemId(int position) {return 0;}@Overridepublic View getView(final int position, View convertView, ViewGroup parent) {ViewHolder holder = new ViewHolder();if(convertView == null){convertView = getLayoutInflater().inflate(R.layout.test_item , null);convertView.setTag(holder);DragView view = (DragView) convertView.findViewById(R.id.drag_view);views.add(view);view.setOnDragStateListener(new DragView.DragStateListener() {@Overridepublic void onOpened(DragView dragView) {}@Overridepublic void onClosed(DragView dragView) {}@Overridepublic void onForegroundViewClick(DragView dragView , View v) {int pos = (int) dragView.getTag();Toast.makeText(TestActivity.this , "click item" + pos , Toast.LENGTH_SHORT).show();}@Overridepublic void onBackgroundViewClick(DragView dragView , View v) {int pos = (int) dragView.getTag();list.remove(pos);adapter.notifyDataSetChanged();Toast.makeText(TestActivity.this , ((Button)v).getText().toString() + pos , Toast.LENGTH_SHORT).show();}});}else{holder = (ViewHolder) convertView.getTag();}holder.dv = (DragView) convertView.findViewById(R.id.drag_view);holder.dv.setTag(position);holder.dv.close();holder.tv = (TextView) convertView.findViewById(R.id.tv);holder.tv.setText((String) getItem(position));return convertView;}class ViewHolder{DragView dv;TextView tv;}public void close(){for (int i = 0; i < views.size(); i++) {if(views.get(i).isOpen())views.get(i).closeAnim();}}}
}
在122行和104/110行,为了防止有ListView的缓存机制造成数据混乱。这里不同的position对应的点击事件也可以进行封装一下(上面没有处理),并在adaper里提供OnItemClickListener接口来处理点击事件。
好了,也提供一下Activity对应的布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.solo.charge.TestActivity"><com.handmark.pulltorefresh.library.PullToRefreshListViewandroid:id="@+id/listview"android:layout_width="match_parent"android:layout_height="match_parent"/>
</RelativeLayout>
大功告成!
demo.apk
第一次写博客,写得不好、不对的地方还望大家指出。
尊重原创转载请注明出处:http://blog.csdn.net/guitarstudio/article/details/48525339 Power by guitar 侵权必究!
这篇关于一分钟轻松打造左滑删除+下拉刷新Listview控件的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!