android搜索框功能实现_Android实现滑动解锁功能
說到滑動解鎖,就回到了2012~2014年,iPhone4S、5、5S年代,如今準(zhǔn)備踏入2020年,這些年國產(chǎn)機(jī)崛起,再也不是公交車上都是iPhone4S的場景。本篇來使用ViewDragHelper實(shí)現(xiàn)滑動解鎖。
實(shí)現(xiàn)效果:
先來分析一下頁面的元素
背景圖
圓角滑道
圓形滑塊
閃動提示文字
其他一些細(xì)節(jié):
滑道和圓形滑塊之間有些邊距,我們使用padding來處理。
我們需要自定義的就是第2點(diǎn),這個滑道包含一個滑塊的圖片和提示文字,滑塊使用原生ImageView即可,而提示文字則是一個支持漸變著色的TextView(不是重點(diǎn))。
漸變著色的TextView
先秒掉簡單的,漸變著色的TextView,不是重點(diǎn),代碼量不多。
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毫秒執(zhí)行onDraw() postInvalidateDelayed(80); } }}下面重點(diǎn)介紹我們使用ViewDragHelper實(shí)現(xiàn)拽托、滑動的滑道View:SlideLockView
讓滑塊滑動起來
滑道實(shí)際就是一個FrameLayout,我們使用ViewDragHelper將滑塊ImageView進(jìn)行拽托,主要我們要做以下幾件事:
限制拽托的左側(cè)起點(diǎn)、右側(cè)終點(diǎn)(否則滑塊就出去啦!)
松手時判斷滑塊的x坐標(biāo)是偏向滑道的左側(cè)還是右側(cè),來決定滑動到起點(diǎn)還是終點(diǎn)。
滾動結(jié)束,判斷是否到達(dá)了右側(cè)的終點(diǎn)。
判斷拽托速度,如果超過指定速度,則自動滾動滑塊到右側(cè)終點(diǎn)。
看到這4點(diǎn),如果讓我們用事件分發(fā)來處理,代碼量和判斷會非常多,并且需要做速度檢測,而使用ViewDragHelper,上面4點(diǎn)都封裝好啦,我們添加一個回調(diào),再將事件委托給它,在回調(diào)中做事情上面4點(diǎn)的處理,一切都簡單起來了。
創(chuàng)建SlideLockView,繼承FrameLayout
創(chuàng)建ViewDragHelper,使用create靜態(tài)方法創(chuàng)建,有3個參數(shù),第一個拽托控件的父控件(就是當(dāng)前View),第二個參數(shù)是拽托靈敏度,數(shù)值越大,越靈敏,默認(rèn)為1.0,第三個參數(shù)為回調(diào)對象。
委托onInterceptTouchEvent、onTouchEvent事件給ViewDragHelper
找到布局中的滑塊,我們要求滑塊的id為lock_btn,所以需要在ids.xml中預(yù)先定義這個id,如果沒有查找到,則拋出異常。
剩下的事情就在ViewDragHelper的回調(diào)中設(shè)置。
復(fù)寫:
????????tryCaptureView()、
????? ? clampViewPositionHorizontal()、
????? ? clampViewPositionVertical()。
tryCaptureView為判斷子View是否可以拽托
clampViewPositionHorizontal()則是橫向拽托子View時回調(diào),返回可以拽托到的位置。
clampViewPositionVertical則是縱向拽托。
限制滑塊滑動范圍
經(jīng)過上面3個方法重寫,滑塊已經(jīng)可以左右滑動了,但是可以滑動出滑道(父控件),我們需要限制橫向滑動的范圍,不能超過左側(cè)起點(diǎn)和右側(cè)終點(diǎn)。我們需要修改clampViewPositionHorizontal這個方法。
左側(cè)起點(diǎn)的x坐標(biāo),就是paddingStart。
右側(cè)終點(diǎn),為滑道總長度 - 右邊邊距 - 滑塊寬度。
判斷回調(diào)的left值,如果小于起點(diǎn),則強(qiáng)制為起點(diǎn),如果大于右側(cè)終點(diǎn)值,則強(qiáng)制為終點(diǎn)。
這樣處理,滑塊則不會滑出滑道了!代碼量不對,也很清晰。
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橫向滑動時回調(diào),回調(diào)的left,則是可以滑動的左上角x坐標(biāo) int lockBtnWidth = mLockBtn.getWidth(); //限制左右臨界點(diǎn) int fullWidth = slideRail.getWidth(); //最少的左邊 int leftMinDistance = getPaddingStart(); //最多的右邊 int leftMaxDistance = fullWidth - getPaddingEnd() - lockBtnWidth; //修復(fù)兩端的臨界值 if (left < leftMinDistance) { return leftMinDistance; } else if (left > leftMaxDistance) { return leftMaxDistance; } return left; } //... });}松手回彈和速度檢測
有了滑動和限制滑動范圍,我們還有一個松手回彈和速度檢測,ViewDragHelper同樣給我們封裝了,提供了一個onViewReleased()回調(diào),并且做了速度檢測,將速度也回傳給了我們。
復(fù)寫onViewCaptured(),主要是為了獲取一開始捕獲到滑塊時,他的top值。
復(fù)寫onViewReleased(),主要是計算松手時,滑塊比較近起點(diǎn)還比較近是終點(diǎn),使用ViewDragHelper的settleCapturedViewAt()方法,開始彈性滾動滑塊去到起點(diǎn)或終點(diǎn)。
判斷中,我們添加判斷速度是否超過1000,如果超過,即使拽托距離比較小,就當(dāng)為fling操作,讓滑塊滾動到終點(diǎn)。
settleCapturedViewAt()這個方法,內(nèi)部是使用Scroller進(jìn)行彈性滾動的,所以我們需要復(fù)寫父View的computeScroll()方法,進(jìn)行內(nèi)容滾動處理。
如果不知道為什么這么做,搜索一下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時回調(diào),獲取頂部距離 mTop = capturedChild.getTop(); } @Override public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) { super.onViewReleased(releasedChild, xvel, yvel); //獲取滑塊當(dāng)前的位置 int currentLeft = releasedChild.getLeft(); //獲取滑塊的寬度 int lockBtnWidth = mLockBtn.getWidth(); //獲取滑道寬度 int fullWidth = slideRail.getWidth(); //一般滑道的寬度,用來判斷滑塊距離起點(diǎn)近還是終點(diǎn)近 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(); //判斷是否移動到頭了,未到頭則繼續(xù) if (mViewDragHelper != null) { if (mViewDragHelper.continueSettling(true)) { invalidate(); } }}解鎖回調(diào)
經(jīng)過上面的編碼,滑動解鎖就完成了,但還差一個解鎖回調(diào),進(jìn)行解鎖操作,并且我們需要一個時機(jī)知道滾動結(jié)束了(ViewDragHelper狀態(tài)回調(diào),滾動閑置了,并且滑塊位于終點(diǎn),則為解鎖完成)。
復(fù)寫onViewDragStateChanged()方法,處理ViewDragHelper狀態(tài)改變,狀態(tài)主要有以下3個:
STATE_IDLE = 0,滾動閑置,可以認(rèn)為滾動停止了。
STATE_DRAGGING = 1,正在拽托。
STATE_SETTLING = 2,fling操作時。
提供Callback接口回調(diào)和設(shè)置方法。
我們在onViewDragStateChanged()回調(diào)中判斷,狀態(tài)為STATE_IDLE,并且滑塊位置為終點(diǎn)值時,就為解鎖,并且回調(diào)Callback對象。
public class SlideLockView extends FrameLayout { /** * 回調(diào) */ 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(); //限制左右臨界點(diǎn) int fullWidth = slideRail.getWidth(); //最多的右邊 int leftMaxDistance = fullWidth - getPaddingEnd() - lockBtnWidth; int left = mLockBtn.getLeft(); if (state == ViewDragHelper.STATE_IDLE) { //移動到最右邊,解鎖完成 if (left == leftMaxDistance) { //未解鎖才進(jìn)行解鎖回調(diào),由于這個判斷會進(jìn)兩次,所以做了標(biāo)志位限制 if (!isUnlock) { isUnlock = true; if (mCallback != null) { mCallback.onUnlock(); } } } } } }); } public interface Callback { /** * 當(dāng)解鎖時回調(diào) */ void onUnlock(); } public void setCallback(Callback callback) { mCallback = callback; }}完整代碼
public class SlideLockView extends FrameLayout { /** * 滑動滑塊 */ private View mLockBtn; /** * 拽托幫助類 */ private ViewDragHelper mViewDragHelper; /** * 回調(diào) */ 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,這里會遍歷內(nèi)部子控件來決定是否可以拽托,我們只需要滑塊可以滑動 return child == mLockBtn; } @Override public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) { //拽托子View橫向滑動時回調(diào),回調(diào)的left,則是可以滑動的左上角x坐標(biāo) int lockBtnWidth = mLockBtn.getWidth(); //限制左右臨界點(diǎn) int fullWidth = slideRail.getWidth(); //最少的左邊 int leftMinDistance = getPaddingStart(); //最多的右邊 int leftMaxDistance = fullWidth - getPaddingEnd() - lockBtnWidth; //修復(fù)兩端的臨界值 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縱向滑動時回調(diào),鎖定頂部padding距離即可,不能不復(fù)寫,否則少了頂部的padding,位置就偏去上面了 return getPaddingTop(); } @Override public void onViewCaptured(@NonNull View capturedChild, int activePointerId) { super.onViewCaptured(capturedChild, activePointerId); //捕獲到拽托的View時回調(diào),獲取頂部距離 mTop = capturedChild.getTop(); } @Override public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) { super.onViewReleased(releasedChild, xvel, yvel); //獲取滑塊當(dāng)前的位置 int currentLeft = releasedChild.getLeft(); //獲取滑塊的寬度 int lockBtnWidth = mLockBtn.getWidth(); //獲取滑道寬度 int fullWidth = slideRail.getWidth(); //一般滑道的寬度,用來判斷滑塊距離起點(diǎn)近還是終點(diǎn)近 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(); //限制左右臨界點(diǎn) int fullWidth = slideRail.getWidth(); //最多的右邊 int leftMaxDistance = fullWidth - getPaddingEnd() - lockBtnWidth; int left = mLockBtn.getLeft(); if (state == ViewDragHelper.STATE_IDLE) { //移動到最右邊,解鎖完成 if (left == leftMaxDistance) { //未解鎖才進(jìn)行解鎖回調(diào),由于這個判斷會進(jìn)兩次,所以做了標(biāo)志位限制 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(); //判斷是否移動到頭了,未到頭則繼續(xù) if (mViewDragHelper != null) { if (mViewDragHelper.continueSettling(true)) { invalidate(); } } } public interface Callback { /** * 當(dāng)解鎖時回調(diào) */ void onUnlock(); } public void setCallback(Callback callback) { mCallback = callback; }}基本使用
Xml控件布局
Java代碼
項(xiàng)目Github地址:
(https://github.com/hezihaog/SlideLockScreen)
到這里就結(jié)束了.
往期精彩回顧:
Android自定義實(shí)現(xiàn)箭頭的繪制
Android自定義實(shí)現(xiàn)六邊形按鈕的繪制
Android使用Glide加載清晰長圖
Android開源圖表庫MpAndroidChart的使用
Android實(shí)現(xiàn)商城購物車功能
總結(jié)
以上是生活随笔為你收集整理的android搜索框功能实现_Android实现滑动解锁功能的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 杀疯了!Redmi K60降价后努比亚Z
- 下一篇: 猛男最爱!Xbox手柄新配色“浪漫粉”今