说到滑动解锁,就回到了2012~2014年,iPhone4S、5、5S年代,如今准备踏入2020年,这些年国产机崛起,再也不是公交车上都是iPhone4S的场景。本篇来使用ViewDragHelper实现滑动解锁。

实现效果:

先来分析一下页面的元素

  1. 背景图

  2. 圆角滑道

  3. 圆形滑块

  4. 闪动提示文字

其他一些细节:

  1. 滑道和圆形滑块之间有些边距,我们使用padding来处理。

我们需要自定义的就是第2点,这个滑道包含一个滑块的图片和提示文字,滑块使用原生ImageView即可,而提示文字则是一个支持渐变着色的TextView(不是重点)。

渐变着色的TextView

先秒掉简单的,渐变着色的TextView,不是重点,代码量不多。

public class ShineTextView extends TextView {    private LinearGradient mLinearGradient;    private Matrix mGradientMatrix;    private int mViewWidth = 0;    private int mTranslate = 0;    public ShineTextView(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        if (mViewWidth == 0) {            mViewWidth = getMeasuredWidth();            if (mViewWidth > 0) {                Paint paint = getPaint();                mLinearGradient = new LinearGradient(0,                        0,                        mViewWidth,                        0,                        new int[]{getCurrentTextColor(), 0xffffffff, getCurrentTextColor()},                        null,                        Shader.TileMode.CLAMP);                paint.setShader(mLinearGradient);                mGradientMatrix = new Matrix();            }        }    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        if (mGradientMatrix != null) {            mTranslate += mViewWidth / 5;            if (mTranslate > 2 * mViewWidth) {                mTranslate = -mViewWidth;            }            mGradientMatrix.setTranslate(mTranslate, 0);            mLinearGradient.setLocalMatrix(mGradientMatrix);            //每80毫秒执行onDraw()            postInvalidateDelayed(80);        }    }}

下面重点介绍我们使用ViewDragHelper实现拽托、滑动的滑道View:SlideLockView

让滑块滑动起来

滑道实际就是一个FrameLayout,我们使用ViewDragHelper将滑块ImageView进行拽托,主要我们要做以下几件事:

  • 限制拽托的左侧起点、右侧终点(否则滑块就出去啦!)

  • 松手时判断滑块的x坐标是偏向滑道的左侧还是右侧,来决定滑动到起点还是终点。

  • 滚动结束,判断是否到达了右侧的终点。

  • 判断拽托速度,如果超过指定速度,则自动滚动滑块到右侧终点。

看到这4点,如果让我们用事件分发来处理,代码量和判断会非常多,并且需要做速度检测,而使用ViewDragHelper,上面4点都封装好啦,我们添加一个回调,再将事件委托给它,在回调中做事情上面4点的处理,一切都简单起来了。

  • 创建SlideLockView,继承FrameLayout

public class SlideLockView extends FrameLayout {    /**     * 拽托帮助类     */    private ViewDragHelper mViewDragHelper;        public SlideLockView(@NonNull Context context) {        this(context, null);    }    public SlideLockView(@NonNull Context context, @Nullable AttributeSet attrs) {        this(context, attrs, 0);    }    public SlideLockView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init(context, attrs, defStyleAttr);    }    private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        //进行初始化...    }}
  • 创建ViewDragHelper,使用create静态方法创建,有3个参数,第一个拽托控件的父控件(就是当前View),第二个参数是拽托灵敏度,数值越大,越灵敏,默认为1.0,第三个参数为回调对象。

private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {    final SlideLockView slideRail = this;    mViewDragHelper = ViewDragHelper.create(this, 0.3f, new ViewDragHelper.Callback() {        ...    });}
  • 委托onInterceptTouchEvent、onTouchEvent事件给ViewDragHelper

@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {    //将onInterceptTouchEvent委托给ViewDragHelper    return mViewDragHelper.shouldInterceptTouchEvent(ev);}    @Overridepublic boolean onTouchEvent(MotionEvent event) {    //将onTouchEvent委托给ViewDragHelper    mViewDragHelper.processTouchEvent(event);    return true;}
  • 找到布局中的滑块,我们要求滑块的id为lock_btn,所以需要在ids.xml中预先定义这个id,如果没有查找到,则抛出异常。

//文件名:ids.xml<?xml version="1.0" encoding="utf-8"?><resources>    <item name="lock_btn" type="id" />resources>
@Overrideprotected void onFinishInflate() {    super.onFinishInflate();    //找到需要拽托的滑块    mLockBtn = findViewById(R.id.lock_btn);    if (mLockBtn == null) {        throw new NullPointerException("必须要有一个滑动滑块");    }}
  • 剩下的事情就在ViewDragHelper的回调中设置。

复写:

tryCaptureView()、

clampViewPositionHorizontal()、

clampViewPositionVertical()。

  • tryCaptureView为判断子View是否可以拽托

  • clampViewPositionHorizontal()则是横向拽托子View时回调,返回可以拽托到的位置。

  • clampViewPositionVertical则是纵向拽托。

private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {    final SlideLockView slideRail = this;    mViewDragHelper = ViewDragHelper.create(this, 0.3f, new ViewDragHelper.Callback() {        @Override        public boolean tryCaptureView(@NonNull View child, int pointerId) {            //判断能拽托的View,这里会遍历内部子控件来决定是否可以拽托,我们只需要滑块可以滑动            return child == mLockBtn;        }                @Override        public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {            //拽托子View横向滑动时回调,回调的left,则是可以滑动的左上角x坐标            return left;        }        @Override        public int clampViewPositionVertical(@NonNull View child, int top, int dy) {            //拽托子View纵向滑动时回调,锁定顶部padding距离即可,不能不复写,否则少了顶部的padding,位置就偏去上面了            return getPaddingTop();        }    });}

限制滑块滑动范围

  • 经过上面3个方法重写,滑块已经可以左右滑动了,但是可以滑动出滑道(父控件),我们需要限制横向滑动的范围,不能超过左侧起点和右侧终点。我们需要修改clampViewPositionHorizontal这个方法。

  • 左侧起点的x坐标,就是paddingStart。

  • 右侧终点,为滑道总长度 - 右边边距 - 滑块宽度。

  • 判断回调的left值,如果小于起点,则强制为起点,如果大于右侧终点值,则强制为终点。

这样处理,滑块则不会滑出滑道了!代码量不对,也很清晰。

private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {    final SlideLockView slideRail = this;    mViewDragHelper = ViewDragHelper.create(this, 0.3f, new ViewDragHelper.Callback() {        //...                @Override        public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {            //拽托子View横向滑动时回调,回调的left,则是可以滑动的左上角x坐标            int lockBtnWidth = mLockBtn.getWidth();            //限制左右临界点            int fullWidth = slideRail.getWidth();            //最少的左边            int leftMinDistance = getPaddingStart();            //最多的右边            int leftMaxDistance = fullWidth - getPaddingEnd() - lockBtnWidth;            //修复两端的临界值            if (left < leftMinDistance) {                return leftMinDistance;            } else if (left > leftMaxDistance) {                return leftMaxDistance;            }            return left;        }        //...    });}

松手回弹和速度检测

有了滑动和限制滑动范围,我们还有一个松手回弹和速度检测,ViewDragHelper同样给我们封装了,提供了一个onViewReleased()回调,并且做了速度检测,将速度也回传给了我们。

  • 复写onViewCaptured(),主要是为了获取一开始捕获到滑块时,他的top值。

  • 复写onViewReleased(),主要是计算松手时,滑块比较近起点还比较近是终点,使用ViewDragHelper的settleCapturedViewAt()方法,开始弹性滚动滑块去到起点或终点。

  • 判断中,我们添加判断速度是否超过1000,如果超过,即使拽托距离比较小,就当为fling操作,让滑块滚动到终点。

settleCapturedViewAt()这个方法,内部是使用Scroller进行弹性滚动的,所以我们需要复写父View的computeScroll()方法,进行内容滚动处理。

如果不知道为什么这么做,搜索一下Scroller的资料了解一下~

private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {    final SlideLockView slideRail = this;    mViewDragHelper = ViewDragHelper.create(this, 0.3f, new ViewDragHelper.Callback() {        private int mTop;                //...                @Override        public void onViewCaptured(@NonNull View capturedChild, int activePointerId) {            super.onViewCaptured(capturedChild, activePointerId);            //捕获到拽托的View时回调,获取顶部距离            mTop = capturedChild.getTop();        }        @Override        public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {            super.onViewReleased(releasedChild, xvel, yvel);            //获取滑块当前的位置            int currentLeft = releasedChild.getLeft();            //获取滑块的宽度            int lockBtnWidth = mLockBtn.getWidth();            //获取滑道宽度            int fullWidth = slideRail.getWidth();            //一般滑道的宽度,用来判断滑块距离起点近还是终点近            int halfWidth = fullWidth / 2;            //松手位置在小于一半,并且滑动速度小于1000,则回到左边            if (currentLeft <= halfWidth && xvel < 1000) {                mViewDragHelper.settleCapturedViewAt(getPaddingStart(), mTop);            } else {                //否则去到右边(宽度,减去padding和滑块宽度)                mViewDragHelper.settleCapturedViewAt(fullWidth - getPaddingEnd() - lockBtnWidth, mTop);            }            invalidate();        }    });}@Overridepublic void computeScroll() {    super.computeScroll();    //判断是否移动到头了,未到头则继续    if (mViewDragHelper != null) {        if (mViewDragHelper.continueSettling(true)) {            invalidate();        }    }}

解锁回调

经过上面的编码,滑动解锁就完成了,但还差一个解锁回调,进行解锁操作,并且我们需要一个时机知道滚动结束了(ViewDragHelper状态回调,滚动闲置了,并且滑块位于终点,则为解锁完成)。

  • 复写onViewDragStateChanged()方法,处理ViewDragHelper状态改变,状态主要有以下3个:

  1. STATE_IDLE = 0,滚动闲置,可以认为滚动停止了。

  2. STATE_DRAGGING = 1,正在拽托。

  3. STATE_SETTLING = 2,fling操作时。

  • 提供Callback接口回调和设置方法。

我们在onViewDragStateChanged()回调中判断,状态为STATE_IDLE,并且滑块位置为终点值时,就为解锁,并且回调Callback对象。

public class SlideLockView extends FrameLayout {    /**     * 回调     */    private Callback mCallback;    /**     * 是否解锁     */    private boolean isUnlock = false;        private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        final SlideLockView slideRail = this;        mViewDragHelper = ViewDragHelper.create(this, 0.3f, new ViewDragHelper.Callback() {            private int mTop;                        //...                        @Override            public void onViewDragStateChanged(int state) {                super.onViewDragStateChanged(state);                int lockBtnWidth = mLockBtn.getWidth();                //限制左右临界点                int fullWidth = slideRail.getWidth();                //最多的右边                int leftMaxDistance = fullWidth - getPaddingEnd() - lockBtnWidth;                int left = mLockBtn.getLeft();                if (state == ViewDragHelper.STATE_IDLE) {                    //移动到最右边,解锁完成                    if (left == leftMaxDistance) {                        //未解锁才进行解锁回调,由于这个判断会进两次,所以做了标志位限制                        if (!isUnlock) {                            isUnlock = true;                            if (mCallback != null) {                                mCallback.onUnlock();                            }                        }                    }                }            }        });    }    public interface Callback {        /**         * 当解锁时回调         */        void onUnlock();    }    public void setCallback(Callback callback) {        mCallback = callback;    }}

完整代码

public class SlideLockView extends FrameLayout {    /**     * 滑动滑块     */    private View mLockBtn;    /**     * 拽托帮助类     */    private ViewDragHelper mViewDragHelper;    /**     * 回调     */    private Callback mCallback;    /**     * 是否解锁     */    private boolean isUnlock = false;    public SlideLockView(@NonNull Context context) {        this(context, null);    }    public SlideLockView(@NonNull Context context, @Nullable AttributeSet attrs) {        this(context, attrs, 0);    }    public SlideLockView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init(context, attrs, defStyleAttr);    }    private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        final SlideLockView slideRail = this;        mViewDragHelper = ViewDragHelper.create(this, 0.3f, new ViewDragHelper.Callback() {            private int mTop;            @Override            public boolean tryCaptureView(@NonNull View child, int pointerId) {                //判断能拽托的View,这里会遍历内部子控件来决定是否可以拽托,我们只需要滑块可以滑动                return child == mLockBtn;            }            @Override            public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {                //拽托子View横向滑动时回调,回调的left,则是可以滑动的左上角x坐标                int lockBtnWidth = mLockBtn.getWidth();                //限制左右临界点                int fullWidth = slideRail.getWidth();                //最少的左边                int leftMinDistance = getPaddingStart();                //最多的右边                int leftMaxDistance = fullWidth - getPaddingEnd() - lockBtnWidth;                //修复两端的临界值                if (left < leftMinDistance) {                    return leftMinDistance;                } else if (left > leftMaxDistance) {                    return leftMaxDistance;                }                return left;            }            @Override            public int clampViewPositionVertical(@NonNull View child, int top, int dy) {                //拽托子View纵向滑动时回调,锁定顶部padding距离即可,不能不复写,否则少了顶部的padding,位置就偏去上面了                return getPaddingTop();            }            @Override            public void onViewCaptured(@NonNull View capturedChild, int activePointerId) {                super.onViewCaptured(capturedChild, activePointerId);                //捕获到拽托的View时回调,获取顶部距离                mTop = capturedChild.getTop();            }            @Override            public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {                super.onViewReleased(releasedChild, xvel, yvel);                //获取滑块当前的位置                int currentLeft = releasedChild.getLeft();                //获取滑块的宽度                int lockBtnWidth = mLockBtn.getWidth();                //获取滑道宽度                int fullWidth = slideRail.getWidth();                //一般滑道的宽度,用来判断滑块距离起点近还是终点近                int halfWidth = fullWidth / 2;                //松手位置在小于一半,并且滑动速度小于1000,则回到左边                if (currentLeft <= halfWidth && xvel < 1000) {                    mViewDragHelper.settleCapturedViewAt(getPaddingStart(), mTop);                } else {                    //否则去到右边(宽度,减去padding和滑块宽度)                    mViewDragHelper.settleCapturedViewAt(fullWidth - getPaddingEnd() - lockBtnWidth, mTop);                }                invalidate();            }            @Override            public void onViewDragStateChanged(int state) {                super.onViewDragStateChanged(state);                int lockBtnWidth = mLockBtn.getWidth();                //限制左右临界点                int fullWidth = slideRail.getWidth();                //最多的右边                int leftMaxDistance = fullWidth - getPaddingEnd() - lockBtnWidth;                int left = mLockBtn.getLeft();                if (state == ViewDragHelper.STATE_IDLE) {                    //移动到最右边,解锁完成                    if (left == leftMaxDistance) {                        //未解锁才进行解锁回调,由于这个判断会进两次,所以做了标志位限制                        if (!isUnlock) {                            isUnlock = true;                            if (mCallback != null) {                                mCallback.onUnlock();                            }                        }                    }                }            }        });    }    @Override    protected void onFinishInflate() {        super.onFinishInflate();        //找到需要拽托的滑块        mLockBtn = findViewById(R.id.lock_btn);        if (mLockBtn == null) {            throw new NullPointerException("必须要有一个滑动滑块");        }    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        //将onInterceptTouchEvent委托给ViewDragHelper        return mViewDragHelper.shouldInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        //将onTouchEvent委托给ViewDragHelper        mViewDragHelper.processTouchEvent(event);        return true;    }    @Override    public void computeScroll() {        super.computeScroll();        //判断是否移动到头了,未到头则继续        if (mViewDragHelper != null) {            if (mViewDragHelper.continueSettling(true)) {                invalidate();            }        }    }    public interface Callback {        /**         * 当解锁时回调         */        void onUnlock();    }    public void setCallback(Callback callback) {        mCallback = callback;    }}

基本使用

  • Xml控件布局

<?xml version="1.0" encoding="utf-8"?><FrameLayout 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"    android:background="@drawable/app_lock_screen_bg"    tools:context=".ScreenLockActivity">    <com.zh.android.slidelockscreen.widget.SlideLockView        android:id="@+id/slide_rail"        android:layout_width="300dp"        android:layout_height="wrap_content"        android:layout_gravity="bottom|center_horizontal"        android:layout_marginBottom="20dp"        android:background="@drawable/app_slide_rail_bg"        android:paddingStart="6dp"        android:paddingTop="8dp"        android:paddingEnd="6dp"        android:paddingBottom="8dp">        <com.zh.android.slidelockscreen.widget.ShineTextView            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_gravity="center"            android:layout_marginStart="20dp"            android:gravity="center"            android:text="右滑解锁打开应用"            android:textColor="@color/app_tip_text"            android:textSize="18sp" />        <ImageView            android:id="@id/lock_btn"            android:layout_width="48dp"            android:layout_height="48dp"            android:src="@drawable/app_lock_btn" />    com.zh.android.slidelockscreen.widget.SlideLockView>FrameLayout>
  • Java代码

public class ScreenLockActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_screen_lock);        SlideLockView slideRail = findViewById(R.id.slide_rail);        slideRail.setCallback(new SlideLockView.Callback() {            @Override            public void onUnlock() {                //解锁,跳转到首页                Intent intent = new Intent(ScreenLockActivity.this, HomeActivity.class);                startActivity(intent);                finish();            }        });    }}

项目Github地址:

(https://github.com/hezihaog/SlideLockScreen)

到这里就结束了.

往期精彩回顾:

  • Android自定义实现箭头的绘制

  • Android自定义实现六边形按钮的绘制

  • Android使用Glide加载清晰长图

  • Android开源图表库MpAndroidChart的使用

  • Android实现商城购物车功能

android搜索框功能实现_Android实现滑动解锁功能相关推荐

  1. android搜索框布局文件,android搜索框列表布局,流程及主要步骤思维导图

    android搜索框列表布局,流程及主要步骤思维导图 android搜索框列表布局,流程及主要步骤思维导图 activity_coin_search.xml ---------- android:id ...

  2. android搜索框功能实现_巧用 Trie 树,实现搜索引擎关键词提示功能

    来源 | 码海责编 | Carol封图 | CSDN 付费下载于视觉中国我们几乎每天都在用搜索引擎搜索信息,相信大家肯定有注意过这样一个细节:当输入某个字符的时候,搜索引框底下会出现多个推荐词,如下, ...

  3. Android 搜索框、书架页面以及排行榜页面UI设计

    设计思路 收集各大主流小说App搜索UI设置,发现基本都会有在导航栏(AppBar)中设置搜索功能.故本App也采取这种主流设计.同时考虑在书架页面和排行榜页面进行滑动切换. 搜索框UI设计 实现效果 ...

  4. Android搜索框效果

    转载:http://blog.csdn.net/walker02/article/details/7917392 需求:项目中的有关搜索的地方,加上清空文字的功能,目的是为了增加用户体验,使用户删除文 ...

  5. [Android]搜索框SearchView

    SearchView的介绍 SearchView提供了用户界面,并且可以通过监听查询内容来帮助实现搜索查询功能的小组件. SearchView的属性 XML 属性 android:iconifiedB ...

  6. android搜索框实现

    http://blog.csdn.net/pengjianbosoft/article/details/6638402 在Map应用中会经常见到一个浮动的搜索框 一般可以搜索附近的POI点信息 而且这 ...

  7. android 搜索框组件,Android零基础入门|搜索框组件SearchView

    原标题:Android零基础入门|搜索框组件SearchView 一.SearchView概述 SearchView是搜索框组件,它可以让用户在文本框内输入文字,并允许通过监听器监控用户输入,当用户输 ...

  8. android 文本框(textview)左右滑动

    实现android文本框的触摸左右滑动,不需要自定自定义什么的,直接textview就自带了,如下(以左右滑动为列子): 布局文件中定义(事实上这个布局里只配置maxLines 就可以了): < ...

  9. Android 搜索框的实时查询/模糊查询

    在搜索框的检索中我们经常会遇到"精确检索"."模糊检索",精确检索我就不多加解释了,我们看下模糊检索: 参考: http://blog.csdn.net/jds ...

最新文章

  1. 一次win10体验旅程
  2. php 格式化 sub,PHP DateTime sub()用法及代码示例
  3. Java锤子剪刀布大家应该都会玩“锤子剪刀布”的游戏: 现给出两人的交锋记录,请统计双方的胜、平、负次数,并且给出双方分别出什么手势的胜算最大。
  4. java中 静态方法与成员方法何时使用
  5. 【牛客 - 369A】小D的剧场(线性dp)
  6. python内存管理机制错误_Python内存管理机制和垃圾回收机制的简单理解
  7. 浅析libcurl多线程安全问题
  8. python3哪个版本稳定-不要再纠结Python哪个版本好,2020年用Python3就对了
  9. ERP系统容灾方案对ERP生产系统的影响
  10. asp.net ashx + JQuery Ajax + XML
  11. meshlab点云颜色偏暗
  12. git输入 ssh-keygen -t rsa 后只显示Generating public/private rsa key pair. 然后就直接跳出了
  13. 入侵mssql2000
  14. 苹果发布AirPods 3,TWS真无线蓝牙耳机市场活力依旧
  15. kvm直通sata_KVM虚拟化win10显卡直通一例
  16. 属于python保留字的是_属牛的女人全集:属牛女的性格、命运、属相婚配表等-第一星座网...
  17. 重头系统的学习,不会咱就学!2014.6.18
  18. 错误笔记:JavaWeb:请求的资源[/$%7BpageContext.request.contextPath%7D/login]不可用
  19. 华为eNSP模拟器操作技巧之关闭信息提示
  20. 淘宝店铺流量来源有哪些?

热门文章

  1. qt-制作生成dll动态链接库实例
  2. 多线程中堆和栈区别的深入解析
  3. 使用函数指针实现父类函数调用子类函数的两种方式
  4. 浅谈C#中Control的Invoke与BeginInvoke在主副线程中的执行顺序和区别
  5. python 递归 写平方_Python算法:推导、递归和规约
  6. python京东抢购脚本_五个月抢京东抢茅台心得
  7. 未定义标识符 stringc/c++(20)_到 2024 年,阿斯顿·马丁汽车销量的 20%以上将是电动汽车...
  8. win定时关机_怎么让电脑定时关机,有多种办法
  9. osg多视景器实现投影墙
  10. Android网络请求开源框架retrofit的基本GET用法(2.4版本)