preface

As a new authentication method, sliding unlock has rapidly become a popular authentication method because of its convenience and simplicity. The image sliding unlocking is more efficient and secure than sliding unlocking, and has become the most popular user authentication method in modern times.

Results show

Let’s start by analyzing the elements of the page

  • background
  • The rounded way
  • Circular slider
  • Flash prompt text

Some other details:

  • There is some margin between the slide and the circular slider, which we use padding to handle.
  • What we need to customize is point 2. The slide contains a slider image and prompt text that uses a native ImageView and the prompt text is a TextView that supports gradient coloring (not important).

Implementation steps

TextView with gradient coloring

First, drop the simple, gradient colored TextView, not the point, not much code.

Public class ShineTextView extends TextView { LinearGradient, RadialGradient, // SweepGradient. All three gradients are inherited from Android.graphics.shader, and the Paint class supports gradients through the setShader() method. 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); // Execute onDraw() postInvalidateDelayed(80) every 80 milliseconds; }}}Copy the code

Here we focus on the use of ViewDragHelper to implement drag, slide slide View:SlideLockView

Let the slider slide

The slide is actually a FrameLayout, and we use ViewDragHelper to drag the slider ImageView. We do the following things:

  • Limit the left start and right end of the drag support (otherwise the slider will go out!)
  • When you let go, judge whether the x coordinate of the slider is biased to the left or right side of the slide to decide whether to slide to the starting point or the end point.
  • Slide over to determine whether the end point on the right is reached.
  • Judge the drag speed, if the speed exceeds the specified, it will automatically roll the slider to the right end.

If you look at these four points, if you use event distribution, you have a lot of code and judgment, and you need to do speed checks, but if you use ViewDragHelper, all four points are wrapped up, we add a callback, we delegate the event to it, and we do the four points in the callback, and everything is much easier.

  • 1. Create SlideLockView and inherit 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) {// Initialize... }}Copy the code
  • 2. Create a ViewDragHelper using the create static method with 3 parameters. The first parameter is the parent control of the dragHelper control (the current View), the second parameter is the draghelper sensitivity, the higher the value, the more sensitive it is.
private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { final SlideLockView slideRail = this; MViewDragHelper = viewDragHelper. create(this, 0.3f, new viewDragHelper.callback () {... }); }Copy the code
  • Delegate onInterceptTouchEvent and onTouchEvent events to ViewDragHelper
@override public Boolean onInterceptTouchEvent(MotionEvent ev) {// Delegate onInterceptTouchEvent to ViewDragHelper return mViewDragHelper.shouldInterceptTouchEvent(ev); } Override public Boolean onTouchEvent(MotionEvent event) {// Delegate onTouchEvent to ViewDragHelper mViewDragHelper.processTouchEvent(event); return true; }Copy the code
  • Find the slider in the layout. We want the slider id to be LOCK_bTN, so we need to pre-define this ID in ids.xml, and throw an exception if we don’t find it.
// File name: ids.xml <? The XML version = "1.0" encoding = "utf-8"? > <resources> <item name="lock_btn" type="id" /> </resources>Copy the code
@Override protected void onFinishInflate() { super.onFinishInflate(); MLockBtn = findViewById(R.id.lock_btn); If (mLockBtn == null) {throw new NullPointerException(" must have a slider "); }}Copy the code
  • The rest is set up in the ViewDragHelper callback.

Autotype tryCaptureView (), clampViewPositionHorizontal (), clampViewPositionVertical ().

  • TryCaptureView is used to determine whether a child View can be dragged
  • ClampViewPositionHorizontal () is the horizontal drag shim callback when the View, return can be dragged to the position.
  • ClampViewPositionVertical is vertical drag.
private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { final SlideLockView slideRail = this; MViewDragHelper = ViewDragHelper. Create (this, 0.3 f, new ViewDragHelper.Callback() { @Override public boolean tryCaptureView(@NonNull View child, Int pointerId) {return child == mLockBtn; return child == mLockBtn; } @ Override public int clampViewPositionHorizontal (@ NonNull View child, int left, int dx) {/ / drag shim View slide across the callback, the callback left, Return left; return left; } @ Override public int clampViewPositionVertical (@ NonNull View child, int top, int dy) {/ / drag shim View to longitudinal sliding callback, Return getPaddingTop(); return getPaddingTop(); }}); }Copy the code

Limit the slider slide range

  • After rewriting the above 3 methods, the slider can now slide left and right, but can slide out of the slide (parent control), we need to limit the range of horizontal sliding, can not exceed the left start and right end. We need to modify clampViewPositionHorizontal this method.
  • The x coordinate of the starting point on the left is paddingStart.
  • The end point on the right is the total length of the slide – the right margin – the width of the slide block.
  • Determine the left value of the callback. If it is less than the starting point, it is forced to be the starting point; if it is greater than the right end point, it is forced to be the end point.

In this way, the slider will not slide out of the slide! The amount of code is wrong, and it’s clear.

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); 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 = slider.getwidth (); Int halfWidth = fullWidth / 2; // The sliding speed is less than 1000, Back to the left if (currentLeft < = halfWidth && xvel < 1000) {mViewDragHelper. SettleCapturedViewAt (getPaddingStart (), mTop); } else {// otherwise go to the right (width, Minus the padding and slide block width) mViewDragHelper. SettleCapturedViewAt (fullWidth - getPaddingEnd () - lockBtnWidth, mTop); } invalidate(); }}); } @Override public void computeScroll() { super.computeScroll(); If (mViewDragHelper!) if (mViewDragHelper! = null) { if (mViewDragHelper.continueSettling(true)) { invalidate(); }}}Copy the code

Unlock the callback

After coding above, slide unlock is done, but we still need an unlock callback to unlock, and we need a moment to know that the scroll is finished (ViewDragHelper state callback, scroll is idle and the slider is at the end, unlock is done).

  • Overwrite onViewDragStateChanged() to handle changes in ViewDragHelper state, which are as follows:

    1. If STATE_IDLE is 0, scrolling is idle.
    2. STATE_DRAGGING = 1, dragging.
    3. State_demystil = 2, when performing the fling operation.
  • Provides Callback interface Callback and setting methods.

We determine in the onViewDragStateChanged() Callback that the state is STATE_IDLE and the slider position is the end value, and the Callback object is unlocked.

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 = slider.getwidth (); Int leftMaxDistance = fullWidth -getPaddingEnd () -lockBtnWidth; int leftMaxDistance = fullWidth -getPaddingEnd () -lockBtnWidth; int left = mLockBtn.getLeft(); If (state == viewDragHelper.state_idle) {if (left == leftMaxDistance) { So the flag bit is limited if (! isUnlock) { isUnlock = true; if (mCallback ! = null) { mCallback.onUnlock(); }}}}}}); } public interface Callback {/** * Callback when unlock */ void onUnlock(); } public void setCallback(Callback callback) { mCallback = callback; }}Copy the code

The complete code

Public class SlideLockView extends FrameLayout {/** * private View mLockBtn; /** * private ViewDragHelper mViewDragHelper; /** * Callback */ private Callback Callback; / 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) {tryCaptureView(@nonnull View child, int pointerId) { Return child == mLockBtn; } @ Override public int clampViewPositionHorizontal (@ NonNull View child, int left, int dx) {/ / drag shim View slide across the callback, the callback left, Int lockBtnWidth = mlockBtn.getwidth (); Int fullWidth = slider.getwidth (); Int leftMinDistance = getPaddingStart(); Int leftMaxDistance = fullWidth -getPaddingEnd () -lockBtnWidth; int leftMaxDistance = fullWidth -getPaddingEnd () -lockBtnWidth; Left < leftMinDistance) {return leftMinDistance; } else if (left > leftMaxDistance) { return leftMaxDistance; } return left; } @ Override public int clampViewPositionVertical (@ NonNull View child, int top, int dy) {/ / drag shim View to longitudinal sliding callback, Return getPaddingTop(); return getPaddingTop(); } @Override public void onViewCaptured(@NonNull View capturedChild, int activePointerId) { super.onViewCaptured(capturedChild, activePointerId); 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 = slider.getwidth (); Int halfWidth = fullWidth / 2; // The sliding speed is less than 1000, Back to the left if (currentLeft < = halfWidth && xvel < 1000) {mViewDragHelper. SettleCapturedViewAt (getPaddingStart (), mTop); } else {// otherwise go to the right (width, Minus the padding and slide block width) mViewDragHelper. SettleCapturedViewAt (fullWidth - getPaddingEnd () - lockBtnWidth, mTop); } invalidate(); } @Override public void onViewDragStateChanged(int state) { super.onViewDragStateChanged(state); int lockBtnWidth = mLockBtn.getWidth(); Int fullWidth = slider.getwidth (); Int leftMaxDistance = fullWidth -getPaddingEnd () -lockBtnWidth; int leftMaxDistance = fullWidth -getPaddingEnd () -lockBtnWidth; int left = mLockBtn.getLeft(); If (state == viewDragHelper.state_idle) {if (left == leftMaxDistance) { So the flag bit is limited 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(" must have a slider "); }} @override public Boolean onInterceptTouchEvent(MotionEvent ev) {// Delegate onInterceptTouchEvent to ViewDragHelper return mViewDragHelper.shouldInterceptTouchEvent(ev); } Override public Boolean onTouchEvent(MotionEvent event) {// Delegate onTouchEvent to ViewDragHelper mViewDragHelper.processTouchEvent(event); return true; } @Override public void computeScroll() { super.computeScroll(); If (mViewDragHelper!) if (mViewDragHelper! = null) { if (mViewDragHelper.continueSettling(true)) { invalidate(); }} public interface Callback {/** * Callback */ void onUnlock(); } public void setCallback(Callback callback) { mCallback = callback; }}Copy the code

The basic use

Xml control layout

<? The 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" Gravity =" gravity "Android :text=" gravity" Android :text=" gravity "android:text=" gravity" android:text=" gravity "android:text=" gravity" android:text=" gravity" <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>Copy the code

Java code

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); Callback(new slidelockView.callback () {@override public void onUnlock() {// Unlock, Intent Intent = new Intent(screenlockactivity.this, homeactivity.class); startActivity(intent); finish(); }}); }}Copy the code

Github project address

Github.com/ljr7822/Vie…