android 自定义 滑动删除,android自定义View滑动删除效果

View滑动删除效果图

42d1b0fe488f92d7940e809f8ab69f61.gif

实现功能

1、可以向左滑动,右侧出现删除

2、向左滑动如果删除出现一大半,松手打开删除,反之关闭删除

3、应用场景

微信消息的删除功能

实现原理

1、外面是一个ListView

2、条目是一个自定义控件继承ViewGroup

1)、左边一个TextView,右侧屏幕外也有一个TextView

2)、所以继承ViewGroup

实现步骤

1、创建一个SlideDeleteView类

1).构造方法要关联

public class SlideDelete extends ViewGroup {

private View leftView;

private View rightView;

private ViewDragHelper helper;

//第一步关联构造方法

//第二步重写onMeasure和onLViewayout测量子View和布局子View

public SlideDelete(Context context) {

this(context,null);

}

public SlideDelete(Context context, AttributeSet attrs) {

this(context, attrs,0);

}

public SlideDelete(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

helper = ViewDragHelper.create(this, callback);

}

}

2、在布局文件中设置SlideDeleteView里面的子View

SlideDeleteView height=80

TextView

width:matchParent

height:matchparent

TextView

android:id="@+id/container"

android:layout_width="match_parent"

android:layout_height="80dp">

android:id="@+id/content"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:gravity="center"

android:background="#33000000"

android:text="第0个条目"

android:textColor="#fff"

android:textSize="20sp" />

android:id="@+id/delete"

android:layout_width="80dp"

android:layout_height="match_parent"

android:background="#f00"

android:gravity="center"

android:text="删除"

android:textColor="#fff"

android:textSize="20sp" />

3、重写onMeasure,给子View进行测量

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

//对当前组合View的测量,不使用的话,也可以自己设置

measureChildren(widthMeasureSpec,heightMeasureSpec);

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

4、重写onLayout,给子View进行布局

注意事项:要设置leftView,也要设置rightView,不要都写成leftView了

protected void onLayout(boolean changed, int l, int t, int r, int b) {

//第一步获取里面子View

leftView = getChildAt(0);

rightView = getChildAt(1);

//第二步给子View提供相应的布局

int leftL = 0;

int leftT = 0;

int leftR = leftView.getMeasuredWidth();

int leftB = leftView.getMeasuredHeight();

leftView.layout(leftL,leftT,leftR,leftB);

//给rightView提供相应的布局

int rightL = leftView.getMeasuredWidth();

int rightT = 0;

int rightR = leftView.getMeasuredWidth()+ rightView.getMeasuredWidth();

int rightB = rightView.getMeasuredHeight();

rightView.layout(rightL,rightT,rightR,rightB);

}

5、设置View的滑动事件onTouchEvent,实现滑动

1).因为要滑动,所以消费该事件返回true

2).使用ViewDragHelper来实现滑动效果

注意事项:

只能实现leftView的滑动,右侧RightView看不到所以滑动不了

只能给滑动的View设置监听,当滑动的时候,重新设置另一个View的布局跟着滑动

@Override

public boolean onTouchEvent(MotionEvent event) {

//1,要消费该事件,所以直接返回true

//2,使用ViewDragHelper来实现滑动效果

helper.processTouchEvent(event);

return true;

}

6、重写滑动事件的监听onViewPositionChanged解决只有lefView滑动的问题

1).重写的方法是在ViewDragHelper.Callback的子实现类中

2).要实现滑动事件,必须在tryCaptureView方法中返回true

private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {

//手势滑动时

@Override

public boolean tryCaptureView(View child, int pointerId) {

return true;

}

//监听控件移动状态

@Override

public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {

//如果左边控件拖动,我们要让右边控件也重新布局

if(changedView == leftView){

rightView.layout(rightView.getLeft()+dx,0,rightView.getRight()+dx,rightView.getBottom()+dy);

}else if(changedView == rightView){

leftView.layout(leftView.getLeft()+dx,0,leftView.getRight()+dx,leftView.getBottom()+dy);

}

}

7、重写clampViewPositionHorizontal水平位置移动,解决左右越界问题

1.返回值为移动时左侧滑动的距离

2.如果滑动的控件是leftView时,解决越界

3.如果滑动的控件是rightView时,解决越界

public int clampViewPositionHorizontal(View child, int left, int dx) {

//对左右越界问题的处理

if(child == leftView){

//处理两边的越界问题

if(left >= 0){

left = 0;

}else if(left <= -rightView.getMeasuredWidth()){

left = -rightView.getMeasuredWidth();

}

}else if(child == rightView){

//只处理右边的越界问题,因为左侧越界的时看不到该View

if(left <= leftView.getMeasuredWidth()- rightView.getMeasuredWidth()){

left = leftView.getMeasuredWidth()- rightView.getMeasuredWidth();

}else if(left >= leftView.getMeasuredWidth()){

left = leftView.getMeasuredWidth();

}

}

return left;

}

8、手松开时重写onViewReleased方法,实现滑动手松开时,rightView是打开还是关闭

1.使用ViewDragHelper滑动时,要调用invalidate方法,回调computeScroll方法

2.重写computeScroll方法

1).先判断是否要继承滑动

2).使用兼容的invalidate方法来实现匀速滑动

@Override

public void onViewReleased(View releasedChild, float xvel, float yvel) {

//松开后,什么时候打开rightView,什么时候关闭leftView

//临界值,rightView.getLeft() 和 屏幕的宽度-rightView.getWidth()/2

if(releasedChild == leftView){

if(rightView.getLeft() < getMeasuredWidth() - rightView.getMeasuredWidth()/2){

//使用ViewDragHelper来滑动

helper.smoothSlideViewTo(rightView,getMeasuredWidth()-rightView.getMeasuredWidth(),0);

invalidate();

}else{

helper.smoothSlideViewTo(rightView,getMeasuredWidth(),0);

invalidate();

}

}

}

//需要重写computeScroll

@Override

public void computeScroll() {

//判断是否要继承滑动

if(helper.continueSettling(true)){

//invalidate();

//兼容使用

ViewCompat.postInvalidateOnAnimation(this);

}

}

9、实现删除rightView的点击删除事件

1.在listView的adapter中找到右侧的rightView

2.调用rightView的点击事件

3.删除该条目

1)删除集合中的数据

list.remove(position);

2)更新adapter

notifyDataSetChanged();

3)重新绘制整个条目

requestLayout();

//设置删除的点击事件

vh.delete.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

//删除当前的数据

list.remove(position);

notifyDataSetChanged();

//让父容器更新下

}

});

vh.container.requestLayout();

注意事项

1、在重写onLayout方法的时候,给rightView设置布局的时候,写成leftView一直出错

2、在onTouchEvent中要返回true,因为要消费该事件

3、在使用ViewDragHelper.Callback时,重写tryCaptureView时要返回true

4、在onTouchEvent中,不使用scrollby或者scrollTo,而是使用ViewDragHelper工具类,不需要判断滑动的距离

总结

1、首先该控件是自定义View,不是组合控件,因为组合的话rightView在屏幕右侧不能实现

2、是自定义View中的继承ViewGroup,因为左侧leftView和右侧rightView都是TextView不需要自己画

源码

SlideDelete的源码

package com.example.movedelete;

import android.content.Context;

import android.support.v4.view.ViewCompat;

import android.support.v4.widget.ViewDragHelper;

import android.util.AttributeSet;

import android.view.MotionEvent;

import android.view.View;

import android.view.ViewGroup;

/**

* Created by guixin on 2017/1/5.

*/

public class SlideDelete extends ViewGroup {

private View leftView;

private View rightView;

private ViewDragHelper helper;

//第一步关联构造方法

//第二步重写onMeasure和onLViewayout测量子View和布局子View

public SlideDelete(Context context) {

this(context,null);

}

public SlideDelete(Context context, AttributeSet attrs) {

this(context, attrs,0);

}

public SlideDelete(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

helper = ViewDragHelper.create(this, callback);

}

private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {

//手势滑动时

@Override

public boolean tryCaptureView(View child, int pointerId) {

return true;

}

//拖动控件水平移动

@Override

public int clampViewPositionHorizontal(View child, int left, int dx) {

//对左右越界问题的处理

if(child == leftView){

//处理两边的越界问题

if(left >= 0){

left = 0;

}else if(left <= -rightView.getMeasuredWidth()){

left = -rightView.getMeasuredWidth();

}

}else if(child == rightView){

//只处理右边的越界问题,因为左侧越界的时看不到该View

if(left <= leftView.getMeasuredWidth()- rightView.getMeasuredWidth()){

left = leftView.getMeasuredWidth()- rightView.getMeasuredWidth();

}else if(left >= leftView.getMeasuredWidth()){

left = leftView.getMeasuredWidth();

}

}

return left;

}

//监听控件移动状态

@Override

public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {

//如果左边控件拖动,我们要让右边控件也重新布局

if(changedView == leftView){

rightView.layout(rightView.getLeft()+dx,0,rightView.getRight()+dx,rightView.getBottom()+dy);

}else if(changedView == rightView){

leftView.layout(leftView.getLeft()+dx,0,leftView.getRight()+dx,leftView.getBottom()+dy);

}

}

//解决滑动一半松手时,View的复位

/**

*

* @param releasedChild 松开的View

* @param xvel

* @param yvel

*/

@Override

public void onViewReleased(View releasedChild, float xvel, float yvel) {

//松开后,什么时候打开rightView,什么时候关闭leftView

//临界值,rightView.getLeft() 和 屏幕的宽度-rightView.getWidth()/2

if(releasedChild == leftView){

if(rightView.getLeft() < getMeasuredWidth() - rightView.getMeasuredWidth()/2){

//使用ViewDragHelper来滑动

helper.smoothSlideViewTo(rightView,getMeasuredWidth()-rightView.getMeasuredWidth(),0);

invalidate();

}else{

helper.smoothSlideViewTo(rightView,getMeasuredWidth(),0);

invalidate();

}

}

}

};

//需要重写computeScroll

@Override

public void computeScroll() {

//判断是否要继承滑动

if(helper.continueSettling(true)){

//invalidate();

//兼容使用

ViewCompat.postInvalidateOnAnimation(this);

}

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

//对当前组合View的测量,不使用的话,也可以自己设置

measureChildren(widthMeasureSpec,heightMeasureSpec);

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

//第一步获取里面子View

leftView = getChildAt(0);

rightView = getChildAt(1);

//第二步给子View提供相应的布局

int leftL = 0;

int leftT = 0;

int leftR = leftView.getMeasuredWidth();

int leftB = leftView.getMeasuredHeight();

leftView.layout(leftL,leftT,leftR,leftB);

//给rightView提供相应的布局

int rightL = leftView.getMeasuredWidth();

int rightT = 0;

int rightR = leftView.getMeasuredWidth()+ rightView.getMeasuredWidth();

int rightB = rightView.getMeasuredHeight();

rightView.layout(rightL,rightT,rightR,rightB);

}

//View的事件传递

@Override

public boolean onTouchEvent(MotionEvent event) {

//1,要消费该事件,所以直接返回true

//2,使用ViewDragHelper来实现滑动效果

helper.processTouchEvent(event);

return true;

}

}

MainActivity.java源码

package com.example.movedelete;

import android.os.Bundle;

import android.support.v7.app.AppCompatActivity;

import android.widget.ListView;

import com.example.movedelete.adapter.SlideDeleteAdapter;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

private ListView lv;

private ArrayList list;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

//初始化视图

initView();

//初始化数据

initData();

//初始化事件

initEvent();

}

//初始化视图

private void initView() {

lv = (ListView) findViewById(R.id.lv);

}

//初始化数据

private void initData() {

list = new ArrayList<>();

for (int i = 0; i < 20; i++) {

list.add("第"+i+"项条目");

}

}

//初始化事件

private void initEvent() {

SlideDeleteAdapter adapter = new SlideDeleteAdapter(list);

lv.setAdapter(adapter);

}

}

SlideDeleteAdapter.java源码

package com.example.movedelete.adapter;

import android.view.View;

import android.view.ViewGroup;

import android.widget.BaseAdapter;

import android.widget.TextView;

import com.example.movedelete.R;

import com.example.movedelete.SlideDelete;

import java.util.ArrayList;

/**

* Created by guixin on 2017/1/5.

*/

public class SlideDeleteAdapter extends BaseAdapter{

private ArrayList list;

public SlideDeleteAdapter(ArrayList list) {

this.list = list;

}

@Override

public int getCount() {

return list == null ? 0 : list.size();

}

@Override

public String getItem(int position) {

return list == null ? null : list.get(position);

}

@Override

public long getItemId(int position) {

return position;

}

@Override

public View getView(final int position, View convertView, ViewGroup parent) {

ViewHolder vh;

if(convertView == null){

convertView = View.inflate(parent.getContext(), R.layout.item_slide,null);

vh = new ViewHolder(convertView);

convertView.setTag(vh);

}else{

vh = (ViewHolder) convertView.getTag();

}

vh.content.setText(list.get(position));

//设置删除的点击事件

vh.delete.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

//删除当前的数据

list.remove(position);

notifyDataSetChanged();

//让父容器更新下

}

});

vh.container.requestLayout();

return convertView;

}

class ViewHolder{

private TextView content;

private TextView delete;

private SlideDelete container;

public ViewHolder(View v){

container = (SlideDelete) v.findViewById(R.id.container);

content = (TextView) v.findViewById(R.id.content);

delete = (TextView) v.findViewById(R.id.delete);

}

}

}

activity_main.xml

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/activity_main"

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/lv"

android:layout_width="match_parent"

android:layout_height="match_parent">

item_slide.xml

android:orientation="vertical" android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/container"

android:layout_width="match_parent"

android:layout_height="80dp">

android:id="@+id/content"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:gravity="center"

android:background="#33000000"

android:text="第0个条目"

android:textColor="#fff"

android:textSize="20sp" />

android:id="@+id/delete"

android:layout_width="80dp"

android:layout_height="match_parent"

android:background="#f00"

android:gravity="center"

android:text="删除"

android:textColor="#fff"

android:textSize="20sp" />

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值