在学习这个之前,你首先要了解android的消息机制,Android的坐标系统,android View绘制流程

  1. scrollBy 个 scrollTo的区别
    scrollTo:相对View的初始位置移动的距离。
    scrollBy:相对当前位置移动的距离。
    public void scrollTo(int x, int y) {if (mScrollX != x || mScrollY != y) {int oldX = mScrollX;int oldY = mScrollY;mScrollX = x;mScrollY = y;invalidateParentCaches();onScrollChanged(mScrollX, mScrollY, oldX, oldY);if (!awakenScrollBars()) {postInvalidateOnAnimation();}}}
 //mScrollX 当前的X偏移量,mScrollY 当前的Y偏移量public void scrollBy(int x, int y) {scrollTo(mScrollX + x, mScrollY + y);}
  1. 在实际过程中,例如我们想使控件水平向右移动200dp,那么使用方式:
scrollTo(-200,0);

此时用户可能会很奇怪为什么是-200,因为这个偏移量是相对屏幕左上角移出屏幕外的距离。移出屏幕外的为正,屏幕内的为负。

成员变量mScrollX, mScrollY,相对屏幕左上角已经移出屏幕之外的距离。

Note:假如你给一个LinearLayout调用scrollTo()方法,并不是LinearLayout滚动,而是LinearLayout里面的内容进行滚动,比如你想对一个按钮进行滚动,直接用Button调用scrollTo()一定达不到你的需求,大家可以试一试,如果真要对某个按钮进行scrollTo()滚动的话,我们可以在Button外面包裹一层Layout,然后对Layout调用scrollTo()方法。

  1. startScroll && computeScrollOffset 方法
public class ScrollerLayout extends ViewGroup {...@Overridepublic void computeScroll() {super.computeScroll();if(mScroller.computeScrollOffset()){scrollTo(mScroller.getCurrX(), mScroller.getCurrY());invalidate();}}
}

View滚动的实现原理,我们先调用Scroller的startScroll()方法来进行一些滚动的初始化设置,然后迫使View进行绘制,我们调用View的invalidate()或postInvalidate()就可以重新绘制View,绘制View的时候会触发computeScroll()方法,我们重写computeScroll(),在computeScroll()里面先调用Scroller的computeScrollOffset()方法来判断滚动有没有结束,如果滚动没有结束我们就调用scrollTo()方法来进行滚动,该scrollTo()方法虽然会重新绘制View,但是我们还是要手动调用下invalidate()或者postInvalidate()来触发界面重绘,重新绘制View又触发computeScroll(),所以就进入一个循环阶段,这样子就实现了在某个时间段里面滚动某段距离的一个平滑的滚动效果也许有人会问,为什么不直接调用scrollTo()方法来实现滚动,其实直接调用是可以,只不过scrollTo()是瞬间滚动的,给人的用户体验不太好,所以Android提供了Scroller类实现平滑滚动的效果。为了方面大家理解,我画了一个简单的scroll实现滚动的原理图

  1. Scroller 使用

Scroller使用基本步骤:

  1. 创建Scroller的实例
  2. (可选)判断刷新时机并调用startScroll()方法来初始化滚动数据并刷新界面
  3. (可选)重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
package com.example.qwe;import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;public class ScrollerLayout extends ViewGroup {//滚动实例private Scroller mScroller;//判定为移动的最小像素private int mTouchSlop;//手机按下的时候屏幕坐标private float mXDown;//按住移动的时候屏幕坐标private float mXMove;//上次触发ACTION_MOVE事件时的屏幕坐标private float mXLastMove;//界面可滚动的左边界private int mLeftBorder;//界面可滚动的右边界private int mRightBorder;public ScrollerLayout(Context ctx, AttributeSet attrs){super(ctx,attrs);// 第一步,创建Scroller的实例mScroller = new Scroller(ctx);ViewConfiguration viewConfiguration = ViewConfiguration.get(ctx);// 获取TouchSlop值mTouchSlop = viewConfiguration.getScaledPagingTouchSlop();}//view绘制三部曲:OnMeasure,OnLayout,OnDraw@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int nChildCount = getChildCount();for (int i = 0;i < nChildCount;++i){View childView = getChildAt(i);if (childView.getVisibility() != View.GONE) {measureChild(childView,widthMeasureSpec,heightMeasureSpec);}}}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {if(changed){int nChildCount = getChildCount();for (int i = 0;i < nChildCount;++i){View childView = getChildAt(i);//水平布局这三个控件childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight());}if(nChildCount > 0){mLeftBorder = getChildAt(0).getLeft();mRightBorder = getChildAt(getChildCount() - 1).getRight();}}}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {switch (ev.getAction()){case MotionEvent.ACTION_DOWN:mXDown = ev.getRawX();mXLastMove = ev.getRawX();break;case MotionEvent.ACTION_MOVE:mXMove = ev.getRawX();float diff = Math.abs(mXMove - mXDown);//如果达到最小移动单位,则拦截ACTION_MOVE事件if(diff > mTouchSlop)return true;break;}return super.onInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()){case MotionEvent.ACTION_MOVE:mXMove = event.getRawX();int scrolledX = (int)(mXLastMove - mXMove);if(getScrollX() + scrolledX < mLeftBorder){scrollTo(mLeftBorder,0);return true;}else if(getScrollX() + getWidth() + scrolledX > mRightBorder){scrollTo(mRightBorder - getWidth(),0);return true;}scrollBy(scrolledX,0);mXLastMove = mXMove;break;case MotionEvent.ACTION_UP:// 当手指抬起时,根据当前的滚动值来判定应该滚动到哪个子控件的界面int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();int dx = targetIndex * getWidth() - getScrollX();//这里为什么没用使用scrollTo是为了实现缓冲效果,而不是一下子跳跃滚动// 第二步,调用startScroll()方法来初始化滚动数据并刷新界面//为了配合调用startScroll需要重写computeScroll方法mScroller.startScroll(getScrollX(), 0, dx, 0);invalidate();break;}return super.onTouchEvent(event);}@Overridepublic void computeScroll() {super.computeScroll();// 第三步,重写computeScroll()方法,并在其内部完成平滑滚动的逻辑// 返回false表示滚动完成if(mScroller.computeScrollOffset()){scrollTo(mScroller.getCurrX(), mScroller.getCurrY());invalidate();}}//跳转到指定页public void goToPage(int targetIndex){int dx = targetIndex * getWidth() - getScrollX();mScroller.startScroll(getScrollX(), 0, dx, 0);invalidate();}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".ScrollerActivity"><com.example.qwe.ScrollerLayoutandroid:id="@+id/scroll_Layout"android:layout_width="match_parent"android:layout_height="100dp"><Buttonandroid:layout_width="match_parent"android:layout_height="100dp"android:text="This is first child view"/><Buttonandroid:layout_width="match_parent"android:layout_height="100dp"android:text="This is second child view"/><Buttonandroid:layout_width="match_parent"android:layout_height="100dp"android:text="This is third child view"/></com.example.qwe.ScrollerLayout><Buttonandroid:id="@+id/btn_go_0"android:layout_width="wrap_content"android:layout_height="100dp"android:text="Go To first child view"/><Buttonandroid:id="@+id/btn_go_1"android:layout_width="wrap_content"android:layout_height="100dp"android:text="Go To second child view"/><Buttonandroid:id="@+id/btn_go_2"android:layout_width="wrap_content"android:layout_height="100dp"android:text="Go To third child view"/></LinearLayout>


可能遇见问题:
实际操作过程中我们可能会遇见ACTION_MOVE事件无法触发的现象,这是因为子View没有处理ACTION_DOWN的原因:https://blog.csdn.net/dreamsever/article/details/53907691
参考博客:
Scroller原理: https://www.jianshu.com/p/543b88fa609c
Scroller使用:https://blog.csdn.net/guolin_blog/article/details/48719871


下面是一个防QQ滑动删除控件的实现,如果在RecycleView中使用的话,需要调用setDragListener,处理Drag实现:
drag_item.xml

<?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="60dp"><com.test.demo.DragControlandroid:id="@+id/drag_item"android:layout_width="match_parent"android:layout_height="match_parent"><TextView android:layout_height="match_parent"android:id="@+id/item_text"android:singleLine="true"android:layout_width="match_parent"android:clickable="true"android:background="@color/colorPrimary"/><Button android:layout_height="match_parent"android:id="@+id/item_button"android:text="Del"android:layout_width="200dp"android:background="#ff00ff"/></com.test.demo.DragControl></LinearLayout>

DragControl.java

package com.test.demo;import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.Scroller;import androidx.annotation.Nullable;public class DragControl extends ViewGroup {public interface IDragEvent{void onDragEnd(DragControl dragControl,boolean bDrag);void onDragBegin(DragControl dragControl);void onDragCancel(DragControl dragControl);}static public class DefaultDragEvent implements IDragEvent{private DragControl dragControl = null;@Overridepublic void onDragEnd(DragControl dragControl, boolean bDrag) {if(bDrag){this.dragControl = dragControl;}else{this.dragControl = null;}}@Overridepublic void onDragBegin(DragControl dragControl) {if(this.dragControl != null) {this.dragControl.reset();this.dragControl = null;}}@Overridepublic void onDragCancel(DragControl dragControl) {if(this.dragControl != null) {this.dragControl.reset();this.dragControl = null;}}}private IDragEvent iDragEvent = null;//滚动条private Scroller mScroller;//按住移动的时候屏幕坐标private float mXMove;//按下的时候屏幕坐标private float mXDown = 0;//最后移动的Move坐标private float mXLastMove = 0;//判定为移动的最小像素private int mTouchSlop = 0;//UI边界private int mLeftBorder = 0;private int mRightBorder = 0;//是否开始dragprivate boolean mbDrag = false;public DragControl(Context context, AttributeSet attrs) {super(context, attrs);mScroller = new Scroller(context);ViewConfiguration viewConfiguration = ViewConfiguration.get(context);mTouchSlop = viewConfiguration.getScaledPagingTouchSlop();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int nChildeCount = getChildCount();for (int i = 0;i < nChildeCount;++i){View child = getChildAt(i);if(child.getVisibility() != View.GONE){measureChild(child,widthMeasureSpec,heightMeasureSpec);}}}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {int nChildCount = getChildCount();int nOffsetLeft = 0;for (int i = 0;i < nChildCount;++i){View child = getChildAt(i);child.layout(nOffsetLeft, 0, nOffsetLeft + child.getMeasuredWidth(), child.getMeasuredHeight());nOffsetLeft += child.getMeasuredWidth();}if(nChildCount > 0){mLeftBorder = getChildAt(0).getLeft();mRightBorder = nOffsetLeft;}}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {Log.i("kcc","onInterceptTouchEvent action:" + String.valueOf(ev.getAction()));switch (ev.getAction()){case MotionEvent.ACTION_DOWN:mbDrag = false;mXLastMove = mXDown = ev.getRawX();break;case MotionEvent.ACTION_MOVE:mXMove = ev.getRawX();float diff = Math.abs(mXMove - mXDown);if(diff >mTouchSlop){return true;}break;case MotionEvent.ACTION_CANCEL:if(iDragEvent != null)iDragEvent.onDragCancel(this);break;}return super.onInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.i("kcc","onTouchEvent action:" + String.valueOf(event.getAction()));switch (event.getAction()){case MotionEvent.ACTION_MOVE:getParent().requestDisallowInterceptTouchEvent(true);if(!mbDrag){mbDrag = true;if(iDragEvent != null)iDragEvent.onDragBegin(this);}mXMove = event.getRawX();int xOffset = (int)(mXLastMove - mXMove);//左右边界判断if(getScrollX() + xOffset < mLeftBorder){scrollTo(mLeftBorder,0);return true;}else if(getScrollX() + getWidth() + xOffset > mRightBorder){scrollTo(mRightBorder - getWidth(),0);return true;}scrollBy(xOffset,0);mXLastMove = mXMove;break;case MotionEvent.ACTION_UP:ViewParent parent = getParent();//计算当前在哪个控件位置int nOffsetX = Math.abs(getScrollX()) + getWidth();int nOffsetRight = 0,targetIndex  = 0,lastchildWidth = 0;int nChildCount = getChildCount();for (int i = 0;i < nChildCount;++i){View child = getChildAt(i);lastchildWidth = child.getMeasuredWidth();//如果当前控件滑出距离大于1/2则显示当前滑出控件,否则显示前面一个控件if(nOffsetX <= nOffsetRight + lastchildWidth){if(nOffsetX <= nOffsetRight + lastchildWidth/2){targetIndex = Math.max(0,i -1);}else{targetIndex = i;nOffsetRight += lastchildWidth;}break;}nOffsetRight += lastchildWidth;}int dx = nOffsetRight - getWidth() - getScrollX();mScroller.startScroll(getScrollX(), 0, dx, 0);invalidate();if(iDragEvent != null)iDragEvent.onDragEnd(this,targetIndex != 0);break;}return super.onTouchEvent(event);}@Overridepublic void computeScroll() {super.computeScroll();if(mScroller.computeScrollOffset()){scrollTo(mScroller.getCurrX(), mScroller.getCurrY());invalidate();}}public void reset(){scrollTo(mLeftBorder,0);}public void setDragListener(IDragEvent iDragEvent){this.iDragEvent = iDragEvent;}
}

调用:

 //dragEvent属于Adapter成员变量DragControl.IDragEvent dragEvent = new DragControl.DefaultDragEvent()drag_item = itemView.findViewById(R.id.drag_item);drag_item.setDragListener(IDragEvent);

Scroller的使用及解析(滑动删除)相关推荐

  1. Android 使用Scroller实现绚丽的ListView左右滑动删除Item效果

    本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/17539199) 我在上一篇文章中Android 带你从源码的角度解析 ...

  2. View系列 :源码分析:RecyclerView滑动删除 全解析

    1:效果展示 效果很简单,就是 RecycleView的 滑动删除功能 2:效果分析 主要是三个步骤: 步骤一:是RecyclerView 的每一个条目上增加 删除 View控件,这个是静态xml页面 ...

  3. Android 使用NineOldAndroids实现绚丽的ListView左右滑动删除Item效果

    转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/18311877),请尊重他人的辛勤劳动成果,谢谢! 今天还是 ...

  4. 高仿微信对话列表滑动删除效果

    前言 用过微信的都知道.微信对话列表滑动删除效果是非常不错的,这个效果我们也能够有. 思路事实上非常easy,弄个ListView.然后里面的每一个item做成一个能够滑动的自己定义控件就可以.由于L ...

  5. RecyclerView 梳理:点击长按事件、分割线、拖曳排序、滑动删除

    本文作者 作者:OCNYang 链接:http://www.jianshu.com/p/70788a7a5547 本文由作者投稿发布. 这次主要是把 RecyclerView 比较常用的基本的点,在这 ...

  6. RecyclerView 梳理:点击amp;长按事件、分割线、拖曳排序、滑动删除

    这次主要是把 RecyclerView 比较常用的基本的点,在这里集中整理一下.从这篇文章主要梳理以下几点: 优雅的实现:item 点击事件 & item 长点击事件 RecyclerView ...

  7. UITableViewCell 左侧滑动删除按钮 添加图片 (不完美解决)

    *需求:给cell左侧滑动删除按钮添加图片 //目前的解决方法 链接: https://pan.baidu.com/s/1kVE5gMF 密码: zaph *装态:还在解决 网上查过资料一直没好的解决 ...

  8. Android 编程下代码之(QQ消息列表滑动删除)

       这份代码写出来有些时候了,一直没共享,现在把它共享给大家.简单列一下代码中你可以学到的知识点: 自定义控件的实现方式: 事件的拦截分发消费机制: QQ会话列表滑动删除原理: 最后附上源码链接:Q ...

  9. recycleView 滑动删除Item,拖拽切换Item,你想了解的都在这儿

    滑动删除Item,拖拽切换Item,你想了解的都在这儿 概述 如果上两篇对RecyclerView介绍后,依然没有引起你的兴趣,那么下面关于RecyclerView的使用我相信一定会让你如获珍宝.直接 ...

最新文章

  1. 【SQL】IS NULL and = NULL 在 sql server 中的区别
  2. css3中实现摘取金币_用css3实现抽奖转盘里的扇形图
  3. 《C#图解教程》读书笔记之六:接口和转换
  4. 解决IE8IE9 jquery ajaxj 跨域请求失败的问题。
  5. Android开发之ApiCloud模块开发的注意事项
  6. 基于matlab移位寄存器,基于Matlab产生m序列(DOC X页).doc
  7. thinkpad重装系统不引导_不重装系统修改引导方式为UEFI模式
  8. 2021时间序列-对比学习必读的四篇论文
  9. Photoshop插件--删除暗调通道--脚本开发--PS插件
  10. java项目-第37期基于springboot+layui实现的医院His系统【毕业设计】
  11. 模式与数据库与表的关系
  12. 阿里品牌数据银行:最全数据银行介绍
  13. 恶意软件家族分类 模型集成方案总结
  14. python开始_开始Python的新手教程
  15. android 齿轮动画,Android(Animation): 一直转个不停的齿轮
  16. JWT授权为啥要在 Authorization标头里加个Bearer 呢
  17. python打开本地浏览器_使用webdriver打开本地浏览器--python版
  18. JAVA水晶报表从环境搭建到创建动态水晶报表
  19. 联手中信银行 物品互赠平台宣布“不卖只送”
  20. Lua 错误之 attempt to index a function value

热门文章

  1. hdu 5755 Gambler Bo 三进制高斯消元(开关问题变形)
  2. 7-22 龟兔赛跑 (20分) Python
  3. P4643 [国家集训队]阿狸和桃子的游戏
  4. python模块总结_Python常用模块资料总结和归纳
  5. Muti-Similarity Loss:考虑了batch中整体距离分布的对比损失函数
  6. 二等水准测量记录数据_二等水准测量外业数据整理(往返测)
  7. Vue制作页面在线裁剪功能
  8. python3 教程 下载图片资源
  9. 什么是配置管理?配置管理由专人负责吗?
  10. 领导者必须学会的14个说话技巧!