因?yàn)橐苿釉O(shè)備有限的顯示屏幕,很多時候都需要在合適的時間去隱藏一些控件,比如滑動隱藏就是一個好的設(shè)計(jì)方案。本文將實(shí)現(xiàn)一個通用性較強(qiáng)的滑動隱藏方案,順便采用了GeastureDetector這個好用的用戶動作檢查工具。
一、本文擬實(shí)現(xiàn)的效果圖
最近下載了Now直播APP,發(fā)現(xiàn)它實(shí)現(xiàn)了一個比較流暢的滑動隱藏效果,具體看下面的GIF圖。
因?yàn)樵诶习姹镜哪M器上運(yùn)行,顯得有點(diǎn)卡頓,這不是主要的。界面中有一個綠色的功能按鈕,隨著ListView上滑,它就向下滑動隱藏。能夠看出來它同時還包括了一個變透明的效果。本文將模仿實(shí)現(xiàn)一個這樣的滑動隱藏效果,還是按我的老套路,先上最后的效果圖。
二、具體實(shí)現(xiàn)。
1. 布局文件,主要是一個ScrollView,里面包含了一個textView,另外就是一個功能按鈕。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/textview1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingBottom="1000dp"
android:paddingTop="250dp"
android:text="可滑動的ScrollView"
/>
</ScrollView>
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="20dp"
android:text="功能滑動按鈕"
/>
</RelativeLayout>
2. MainActivity中的主體內(nèi)容,主要干了如下幾件事。
- 其中給ScrollView添加了一個touch監(jiān)聽器
- 處理用戶滑動事件采用了GestureDector
- 初始化和獲取一些關(guān)鍵的成員變量
下面代碼中有一個小細(xì)節(jié),就是用了View.post()方法去獲取mButton的原始top值,為什么要這么干呢?
讀者可以試一試直接獲取,這時候你調(diào)試發(fā)現(xiàn)獲取的值是0;
因?yàn)樵趏nCreate的時候,view的繪制可能還沒有完成,采取view.post()的方法可以解決這個問題。
package com.example.administrator.myapplication;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ScrollView;
public class MainActivity extends AppCompatActivity {
private GestureDetectorCompat mDetectorCompat;
private Button mButton;
private ScrollView mScrollView;
private int mOriginButtonTop;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.button2);
mButton.post(new Runnable() {//post一個線程去獲取button的原始top值
@Override
public void run() {
mOriginButtonTop = mButton.getTop();
}
});
mScrollView = (ScrollView) findViewById(R.id.scrollView);
mDetectorCompat = new GestureDetectorCompat(this, new MyGestureListener());
mScrollView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mDetectorCompat.onTouchEvent(event);
return false;
}
});
}
}
3. 第2步中的MyGestureListener 的實(shí)現(xiàn)。
使用GeastrueDetector必須要傳入一個具體的監(jiān)聽器實(shí)現(xiàn),這個很好理解,因此繼承了GestureDetector.SimpleOnGestureListener來實(shí)現(xiàn)具體的滑動處理邏輯,在它的onScroll()方法中主要干了如下幾件事:
- 首先判斷是不是豎直方向的滑動
- 判斷是向上滑動還是向下滑動
- 根據(jù)不同的滑動方向動態(tài)改變功能按鈕的top和bottom的值,實(shí)現(xiàn)一個移動的效果。
- 因?yàn)槭腔瑒佣嗌倬鸵苿佣嗌伲赃@個效果的連續(xù)性和流暢性還是不錯的。
- 最后注意一些限制,功能按鈕不能向上滑出原本的邊界;向下移動,如果離開了屏幕的可見范圍,就不再移動它。
class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (Math.abs(distanceY) > Math.abs(distanceX)) {//判斷是否豎直滑動
int buttonTop = mButton.getTop();
int buttonBottom = mButton.getBottom();
//是否向下滑動
boolean isScrollDown = e1.getRawY() < e2.getRawY() ? true : false;
//根據(jù)滑動方向和mButton當(dāng)前的位置判斷是否需要移動Button的位置
if (!ifNeedScroll(isScrollDown)) return false;
if (isScrollDown) {
//下滑上移Button
mButton.setTop(buttonTop - (int) Math.abs(distanceY));
mButton.setBottom(buttonBottom - (int) Math.abs(distanceY));
} else if (!isScrollDown) {
//上滑下移Button
mButton.setTop(buttonTop + (int) Math.abs(distanceY));
mButton.setBottom(buttonBottom + (int) Math.abs(distanceY));
}
}
return super.onScroll(e1, e2, distanceX, distanceY);
}
//寫一個方法,根據(jù)滑動方向和mButton當(dāng)前的位置,判斷按鈕是否應(yīng)該繼續(xù)滑動
private boolean ifNeedScroll(boolean isScrollDown) {
int nowButtonTop = mButton.getTop();
//button不能超出原來的上邊界
if (isScrollDown && nowButtonTop <= mOriginButtonTop) return false;
//判斷按鈕是否在屏幕范圍內(nèi),如果不在,則不需要再移動位置
if (!isScrollDown) {
return isInScreen(mButton);
}
return true;
}
}
4. 上一步中注意判斷View是否在屏幕可見范圍內(nèi)的一個方法。
//判斷一個控件是否在屏幕范圍內(nèi)
private boolean isInScreen(View view) {
int width, height;
Point p = new Point();
getWindowManager().getDefaultDisplay().getSize(p);
width = p.x;
height = p.y;
Rect rect = new Rect(0, 0, width, height);
if (!view.getLocalVisibleRect(rect)) return false;
return true;
}
總結(jié)
最后運(yùn)行的效果圖,已經(jīng)在最前面展示過了。本文的滑動隱藏的原理實(shí)現(xiàn)的關(guān)鍵點(diǎn)有如下幾點(diǎn):
滑動的隱藏的原理是移動需要隱藏的控件,直至滑出屏幕外。
本文采取的移動方式是動態(tài)改變控件的top和bottom值,當(dāng)然還有別的方式,比如scrollBy(),setTranslateY(),setPadding(),但是他們有各自的應(yīng)用場景。比如scrollBy()實(shí)際上沒有移動View只是移動了View的內(nèi)容,如果你不希望引起其他View的位置變化,可以采用scrollBy()方法。
本文通過在onScroll()方法里采取滑動多少距離就移動功能按鈕多少距離,保證功能按鈕移動的連續(xù)性,避免一個突兀跳變的問題。
最后,就是注意滑動邊界的判斷,在上述步驟中已經(jīng)說明了。讀者可以不加滑動邊界判斷,試一試是什么效果。
ok,如果覺得本文幫到了你,請留言、點(diǎn)贊,和關(guān)注,期待和你一起進(jìn)步!