Speaking of sliding unlock, it is back to 2012~2014, iPhone4S, 5, 5S era, now ready to step into 2020, these years of domestic machine rise, no longer is the scene of iPhone4S on the bus. This article uses ViewDragHelper to implement sliding unlock.

The finished product to show

Let’s start by analyzing the elements of the page

  1. background
  2. The rounded way
  3. Circular slider
  4. Flash prompt text

Some other details:

  1. 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).

TextView with gradient coloring

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

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); // 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:

  1. Limit the left start and right end of the drag support (otherwise the slider will go out!)
  2. 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.
  3. Scroll over to determine whether the end point on the right has been reached.
  4. 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.

  • 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
  • Create a ViewDragHelper using the create static method. The first parameter is the parent of the dragHelper control (the current View), the second parameter is the draghelper sensitivity (the higher the value, the more sensitive it is), and the third parameter is the callback object.
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 ViewDragHelperreturnmViewDragHelper.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 <? 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("You have to have a slider."); }}Copy the code
  • The rest is set up in the ViewDragHelper callback.

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

  1. TryCaptureView is used to determine whether a child View can be dragged
  2. ClampViewPositionHorizontal () is the horizontal drag shim callback when the View, return can be dragged to the position.
  3. ClampViewPositionVertical is vertical drag.
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) {tryCaptureView(@nonnull View child, int pointerId) We just need the slider to slide aroundreturnchild == mLockBtn; } @ Override public int clampViewPositionHorizontal (@ NonNull View child, int left, int dx) {/ / drag shim View slide across the callback, the callback left, It's the top left x-coordinate that you can slidereturnleft; } @ Override public int clampViewPositionVertical (@ NonNull View child, int top, int dy) {/ / drag shim View to longitudinal sliding callback, Lock the padding distance at the top. You can't overwrite it without the padding at the topreturngetPaddingTop(); }}); }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() {/ /... @ 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 (); // least left int leftMinDistance = getPaddingStart(); Int leftMaxDistance = fullWidth -getPaddingEnd () -lockBtnWidth; int leftMaxDistance = fullWidth -getPaddingEnd () -lockBtnWidth; // Fix the critical values at both endsif (left < leftMinDistance) {
                return leftMinDistance;
            } else if (left > leftMaxDistance) {
                return leftMaxDistance;
            }
            returnleft; } / /... }); }Copy the code

Release rebound and speed detection

With sliding and limiting the sliding range, we also have a release bounce and speed detection. ViewDragHelper also wraps this up for us, provides an onViewReleased() callback, and does speed detection, which also sends speed back to us.

  • OnViewCaptured () is mainly to capture his top value when the slider is first captured.
  • OnViewReleased () is used to calculate whether the slider is closer to the starting point or closer to the end point. Using settleCapturedViewAt() in ViewDragHelper, we start the elastic scrolling of the slider to the starting point or end point.
  • In the judgment, we add whether the speed is more than 1000. If the speed is greater than 1000, even if the distance is small, it is a fling operation and the slider scrolls to the end.

The settleCapturedViewAt() method uses Scroller for elastic scrolling, so we need to duplicate the parent View’s computeScroll() method for content scrolling.

If you’re not sure why you’re doing this, check out Scroller’s profile

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, floatyvel) { super.onViewReleased(releasedChild, xvel, yvel); Int currentLeft = releasedChild.getLeft(); Int lockBtnWidth = mlockBtn.getwidth (); Int fullWidth = slider.getwidth (); Int halfWidth = fullWidth / 2; // If the slide speed is less than 1000, go back to the leftif (currentLeft <= halfWidth && xvel < 1000) {
                mViewDragHelper.settleCapturedViewAt(getPaddingStart(), mTop);
            } else{/ / or go to the right (width, minus padding and slide block width) mViewDragHelper. SettleCapturedViewAt (fullWidth - getPaddingEnd () - lockBtnWidth, mTop); } invalidate(); }}); } @Override public voidcomputeScroll() { super.computeScroll(); // Check if it reaches the end, and continue until it reaches the endif(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; /** * Whether to unlock */ 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) {// Move to the right to unlockif(left == leftMaxDistance) {// The unlock callback is not performed until the unlock is unlockedif(! isUnlock) { isUnlock =true;
                            if(mCallback ! = null) { mCallback.onUnlock(); }}}}}}); } public interface Callback {/** * Callback when unlock */ void onUnlock(); } public voidsetCallback(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; /** * Whether to unlock */ 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) { We just need the slider to slide aroundreturnchild == 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 (); // least left int leftMinDistance = getPaddingStart(); Int leftMaxDistance = fullWidth -getPaddingEnd () -lockBtnWidth; int leftMaxDistance = fullWidth -getPaddingEnd () -lockBtnWidth; // Fix the critical values at both endsif (left < leftMinDistance) {
                    return leftMinDistance;
                } else if (left > leftMaxDistance) {
                    return leftMaxDistance;
                }
                returnleft; } @ Override public int clampViewPositionVertical (@ NonNull View child, int top, int dy) {/ / drag shim View to longitudinal sliding callback, Lock the padding distance at the top. You can't overwrite it without the padding at the topreturngetPaddingTop(); } @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, floatyvel) { super.onViewReleased(releasedChild, xvel, yvel); Int currentLeft = releasedChild.getLeft(); Int lockBtnWidth = mlockBtn.getwidth (); Int fullWidth = slider.getwidth (); Int halfWidth = fullWidth / 2; // If the slide speed is less than 1000, go back to the leftif (currentLeft <= halfWidth && xvel < 1000) {
                    mViewDragHelper.settleCapturedViewAt(getPaddingStart(), mTop);
                } else{/ / or go to the right (width, minus 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) {// Move to the right to unlockif(left == leftMaxDistance) {// The unlock callback is not performed until the unlock is unlockedif(! isUnlock) { isUnlock =true;
                            if(mCallback ! = null) { mCallback.onUnlock(); }}}}}}); } @Override protected voidonFinishInflate() { super.onFinishInflate(); MLockBtn = findViewById(R.id.lock_btn);if (mLockBtn == null) {
            throw new NullPointerException("You have to have a slider."); }} @override public Boolean onInterceptTouchEvent(MotionEvent ev) {// Delegate onInterceptTouchEvent to ViewDragHelperreturnmViewDragHelper.shouldInterceptTouchEvent(ev); } Override public Boolean onTouchEvent(MotionEvent event) {// Delegate onTouchEvent to ViewDragHelper mViewDragHelper.processTouchEvent(event);return true;
    }

    @Override
    public void computeScroll() { super.computeScroll(); // Check if it reaches the end, and continue until it reaches the endif(mViewDragHelper ! = null) {if (mViewDragHelper.continueSettling(true)) { invalidate(); }} public interface Callback {/** * Callback */ void onUnlock(); } public voidsetCallback(Callback callback) { mCallback = callback; }}Copy the code

The basic use

  • Xml control layout
<? 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="Swipe right to unlock and open the app"
            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>
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);
        slideRail.setCallback(new SlideLockView.Callback() {
            @Override
            public void onUnlockIntent Intent = new Intent(screenlockactivity.this, homeactivity.class); startActivity(intent); finish(); }}); }}Copy the code

Github address of the project