尊重原创转载请注明出处: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 file
allprojects {
repositories {
maven { url "https://jitpack.io" }
}
}
步骤 2. Add the dependency
dependencies {
compile 'com.github.guitarstar:SwipeLayout:v1.0'
}
步骤 3. 列表的布局layout编写
<com.solo.library.SlideTouchView
android:id="@+id/mSlideTouchView"
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.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;
}
@Override
public int[] getBindOnClickViewsIds() {
return new int[]{R.id.btn_del}; //必须调用, 删除按钮或者其他你想监听点击事件的View的id
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_list_item, null);
return new MyViewHolder(v);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
MyViewHolder viewHolder = (MyViewHolder) holder;
viewHolder.tv.setText(String.valueOf(list.get(position)));
bindSlidePosition(viewHolder.mSlideTouchView, position);//必须调用
}
@Override
public 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(); }
@Override
public 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() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == fgView;
}
@Override
public int getViewHorizontalDragRange(View child) {
return bgView.getMeasuredWidth();
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return getPositionX(left);
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return 0;
}
@Override
public 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();
}
}
@Override
public 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();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//drag range
if(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;
}
@Override
protected 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 Event
fgView.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);
}
}
@Override
public void computeScroll() {
if(mDrager.continueSettling(true)){
postInvalidate();
}
}
public void setOnDragStateListener(DragStateListener listener){
mDragStateListener = listener;
}
@Override
public 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);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mDrager.shouldInterceptTouchEvent(ev);
}
@Override
public 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();
@Override
protected 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() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
adapter.close();
}
});
mPullToRefreshListView.setOnRefreshListener(new PullToRefreshBase.OnRefreshListener<ListView>() {
@Override
public void onRefresh(PullToRefreshBase<ListView> refreshView) {
//模拟一下刷新数据,额
mHandler.postDelayed(new Runnable() {
@Override
public 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>();
@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(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() {
@Override
public void onOpened(DragView dragView) {
}
@Override
public void onClosed(DragView dragView) {
}
@Override
public void onForegroundViewClick(DragView dragView , View v) {
int pos = (int) dragView.getTag();
Toast.makeText(TestActivity.this , "click item" + pos , Toast.LENGTH_SHORT).show();
}
@Override
public 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.PullToRefreshListView
android: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 侵权必究!