Android制作簡(jiǎn)單垂直上拉下滑View效果
一、簡(jiǎn)介
最近朋友公司需要實(shí)現(xiàn)一個(gè)垂直上拉下滑的View,該View最初只有一部分顯示在屏幕最下方,上拉那一部分可以將該View全部拉出來(lái)并全部顯示在屏幕上,下滑該View可以將該View隱藏在屏幕下。
先看一下最終實(shí)現(xiàn)效果吧。

二、實(shí)現(xiàn)思路
1、這個(gè)效果其實(shí)有很多實(shí)現(xiàn)方法,為了讓松手時(shí)有一個(gè)viewpager一樣的緩慢滑動(dòng)的效果我選擇用scrollBy配合Scroller,應(yīng)該是既方便又實(shí)用的。
2、這個(gè)View的設(shè)計(jì)是這樣的:
(1)將這個(gè)View的子view通過(guò)layout放在該View下面;
(2)通過(guò)重寫onTouchEvent方法給這個(gè)子View滑動(dòng)效果,在MOVE_UP的動(dòng)作給這個(gè)子View加上Scroller平滑到View的頂部或者底部。
見(jiàn)圖:

三、實(shí)現(xiàn)
1、先自定義一個(gè)屬性,表示子View應(yīng)該有多少部分露在外面,也就是上圖中紅色和綠色相交的部分。
在res文件夾-values文件夾下面創(chuàng)建一個(gè)attrs.xml文件
attrs.xml :
<resources> <declare-styleable name="MyScrollerView"> <attr name="visibility_height" format="dimension"></attr> </declare-styleable> </resources>
在XML文件中引用該屬性:
<com.zw.myfirstapp.MyScrollerView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:background="@android:color/transparent" android:id="@+id/msv" app:visibility_height="100dp" ></com.zw.myfirstapp.MyScrollerView>
在代碼中調(diào)用該屬性(該View名字為MyScrollerView,我圖方便繼承的是LinearLayout,繼承ViewGroup或者其他的布局View都可以):
public MyScrollerView(Context context) {
this(context,null);
}
public MyScrollerView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public MyScrollerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.MyScrollerView);
visibilityHeight = ta.getDimension(R.styleable.MyScrollerView_visibility_height,200);
ta.recycle();
init(context);
}
2、重寫onFinishInflate方法,重寫該方法的原因是我希望我只有一個(gè)子View,這樣就好確定滑動(dòng)的高度,不然我還需要重新計(jì)算子View們的高度總和,比較麻煩。這個(gè)方法會(huì)在onMeasure之前調(diào)用。
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if(getChildCount() == 0 || getChildAt(0) == null){
throw new RuntimeException("沒(méi)有子控件!");
}
if(getChildCount() > 1){
throw new RuntimeException("只能有一個(gè)子控件!");
}
mChild = getChildAt(0);
}
3、init方法里做一些初始化操作,比如說(shuō)創(chuàng)建一個(gè)Scroller對(duì)象,給View的背景設(shè)為透明:
private void init(Context context) {
mScroller = new Scroller(context);
this.setBackgroundColor(Color.TRANSPARENT);
}
4、重寫onMeasure方法和onLayout方法,確定可以滑動(dòng)的最大高度,以及子View的排列位置(其實(shí)可以不用重寫onMeasure,我這樣寫只是習(xí)慣)。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mScrollHeight = (int) (mChild.getMeasuredHeight() - visibilityHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mChild.layout(0,mScrollHeight,mChild.getMeasuredWidth(),mChild.getMeasuredHeight() + mScrollHeight);
}
5、先看我定義的成員變量的含義吧:
/** * downY:手指按下時(shí)距離View頂部的距離 * moveY:手指在屏幕上滑動(dòng)的距離(不停變化) * movedY:手指在屏幕上總共滑動(dòng)的距離(為了確定手指一共滑動(dòng)了多少距離,不能超過(guò)可滑動(dòng)的最大距離) */ private int downY,moveY,movedY; //子View private View mChild; private Scroller mScroller; //可滑動(dòng)的最大距離 private int mScrollHeight; //子View是否在頂部 private boolean isTop = false; //最初子View在View內(nèi)可見(jiàn)的高度 private float visibilityHeight;
6、重寫onTouchEvent方法,做滑動(dòng)判斷,解釋都寫在注釋里了:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
//手指按下時(shí)距離View上面的距離
downY = (int) event.getY();
//如果子View不在頂部 && 按下的位置在子View沒(méi)有顯示的位置,則不消費(fèi)此次滑動(dòng)事件,否則消費(fèi)
if(!isTop && downY < mScrollHeight ){
return super.onTouchEvent(event);
}
return true;
case MotionEvent.ACTION_MOVE:
moveY = (int) event.getY();
//deY是滑動(dòng)的距離,向上滑時(shí)deY>0 ,向下滑時(shí)deY<0
int deY = downY - moveY;
//向上滑動(dòng)時(shí)的處理
if(deY > 0){
//將每次滑動(dòng)的距離相加,為了防止子View的滑動(dòng)超過(guò)View的頂部
movedY += deY;
if(movedY > mScrollHeight) movedY = mScrollHeight;
if(movedY < mScrollHeight){
scrollBy(0,deY);
downY = moveY;
return true;
}
}
//向下滑動(dòng)時(shí)的處理,向下滑動(dòng)時(shí)需要判斷子View是否在頂部,如果不在頂部則不消費(fèi)此次事件
if(deY < 0 && isTop){
movedY += deY;
if(movedY < 0 ) movedY = 0;
if(movedY > 0){
scrollBy(0,deY);
}
downY = moveY;
return true;
}
break;
case MotionEvent.ACTION_UP:
//手指抬起時(shí)的處理,如果向上滑動(dòng)的距離超過(guò)了最大可滑動(dòng)距離的1/4,并且子View不在頂部,就表示想把它拉上去
if(movedY > mScrollHeight / 4 && !isTop){
mScroller.startScroll(0,getScrollY(),0,(mScrollHeight - getScrollY()));
invalidate();
movedY = mScrollHeight;
isTop = true;
}else {
//否則就表示放棄本次滑動(dòng),讓它滑到最初的位置
mScroller.startScroll(0,getScrollY(),0, -getScrollY());
postInvalidate();
movedY = 0;
isTop = false;
}
break;
}
return super.onTouchEvent(event);
}
7、最后要重寫一個(gè)computeScroll方法,該方法是用來(lái)配合scroller的:
@Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){
scrollTo(0,mScroller.getCurrY());
postInvalidate();
}
}
8、關(guān)于scroller的用法,可參考郭霖的這篇博客:http://blog.csdn.net/guolin_blog/article/details/48719871
四、完整代碼:
xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.zw.myfirstapp.MyScrollerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@android:color/transparent"
android:id="@+id/msv"
app:visibility_height="100dp"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="@mipmap/b"
android:gravity="center"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn"
android:text="我是一個(gè)按鈕"/>
</LinearLayout>
</com.zw.myfirstapp.MyScrollerView>
</RelativeLayout>
MyScrollerView:
public class MyScrollerView extends LinearLayout {
/**
* downY:手指按下時(shí)距離View頂部的距離
* moveY:手指在屏幕上滑動(dòng)的距離(不停變化)
* movedY:手指在屏幕上總共滑動(dòng)的距離(為了確定手指一共滑動(dòng)了多少距離,不能超過(guò)可滑動(dòng)的最大距離)
*/
private int downY,moveY,movedY;
//子View
private View mChild;
private Scroller mScroller;
//可滑動(dòng)的最大距離
private int mScrollHeight;
//子View是否在頂部
private boolean isTop = false;
//最初子View在View內(nèi)可見(jiàn)的高度
private float visibilityHeight;
public MyScrollerView(Context context) {
this(context,null);
}
public MyScrollerView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public MyScrollerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs,R.styleable.MyScrollerView);
visibilityHeight = ta.getDimension(R.styleable.MyScrollerView_visibility_height,200);
ta.recycle();
init(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mScrollHeight = (int) (mChild.getMeasuredHeight() - visibilityHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mChild.layout(0,mScrollHeight,mChild.getMeasuredWidth(),mChild.getMeasuredHeight() + mScrollHeight);
}
private void init(Context context) {
mScroller = new Scroller(context);
this.setBackgroundColor(Color.TRANSPARENT);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if(getChildCount() == 0 || getChildAt(0) == null){
throw new RuntimeException("沒(méi)有子控件!");
}
if(getChildCount() > 1){
throw new RuntimeException("只能有一個(gè)子控件!");
}
mChild = getChildAt(0);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
//手指按下時(shí)距離View上面的距離
downY = (int) event.getY();
//如果子View不在頂部 && 按下的位置在子View沒(méi)有顯示的位置,則不消費(fèi)此次滑動(dòng)事件,否則消費(fèi)
if(!isTop && downY < mScrollHeight ){
return super.onTouchEvent(event);
}
return true;
case MotionEvent.ACTION_MOVE:
moveY = (int) event.getY();
//deY是滑動(dòng)的距離,向上滑時(shí)deY>0 ,向下滑時(shí)deY<0
int deY = downY - moveY;
//向上滑動(dòng)時(shí)的處理
if(deY > 0){
//將每次滑動(dòng)的距離相加,為了防止子View的滑動(dòng)超過(guò)View的頂部
movedY += deY;
if(movedY > mScrollHeight) movedY = mScrollHeight;
if(movedY < mScrollHeight){
scrollBy(0,deY);
downY = moveY;
return true;
}
}
//向下滑動(dòng)時(shí)的處理,向下滑動(dòng)時(shí)需要判斷子View是否在頂部,如果不在頂部則不消費(fèi)此次事件
if(deY < 0 && isTop){
movedY += deY;
if(movedY < 0 ) movedY = 0;
if(movedY > 0){
scrollBy(0,deY);
}
downY = moveY;
return true;
}
break;
case MotionEvent.ACTION_UP:
//手指抬起時(shí)的處理,如果向上滑動(dòng)的距離超過(guò)了最大可滑動(dòng)距離的1/4,并且子View不在頂部,就表示想把它拉上去
if(movedY > mScrollHeight / 4 && !isTop){
mScroller.startScroll(0,getScrollY(),0,(mScrollHeight - getScrollY()));
invalidate();
movedY = mScrollHeight;
isTop = true;
}else {
//否則就表示放棄本次滑動(dòng),讓它滑到最初的位置
mScroller.startScroll(0,getScrollY(),0, -getScrollY());
postInvalidate();
movedY = 0;
isTop = false;
}
break;
}
return super.onTouchEvent(event);
}
@Override
public void computeScroll() {
super.computeScroll();
if(mScroller.computeScrollOffset()){
scrollTo(0,mScroller.getCurrY());
postInvalidate();
}
}
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android下拉刷新上拉加載控件(適用于所有View)
- android開(kāi)發(fā)教程之實(shí)現(xiàn)listview下拉刷新和上拉刷新效果
- Android實(shí)現(xiàn)上拉加載更多以及下拉刷新功能(ListView)
- Android RecyclerView實(shí)現(xiàn)下拉刷新和上拉加載
- Android RecyclerView 上拉加載更多及下拉刷新功能的實(shí)現(xiàn)方法
- android搜索框上下滑動(dòng)變色效果
- Android中 視頻屏幕左半部分上下滑動(dòng)改變亮度右半部分上下滑動(dòng)改變聲音
- Android RecyclerView下拉刷新和上拉加載更多
- Android自定義listview布局實(shí)現(xiàn)上拉加載下拉刷新功能
- Android實(shí)戰(zhàn)教程第四十三篇之上拉加載與下拉刷新
相關(guān)文章
Android下拉刷新控件SwipeRefreshLayout源碼解析
這篇文章主要為大家詳細(xì)解析Android下拉刷新控件SwipeRefreshLayout源碼,感興趣的小伙伴們可以參考一下2016-07-07
Android 廣播監(jiān)聽(tīng)網(wǎng)絡(luò)狀態(tài)詳解及實(shí)例代碼
這篇文章主要介紹了Android 廣播監(jiān)聽(tīng)網(wǎng)絡(luò)狀態(tài)詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-02-02
以一個(gè)著色游戲展開(kāi)講解Android中區(qū)域圖像填色的方法
這篇文章主要介紹了Android中實(shí)現(xiàn)區(qū)域圖像顏色填充的方法,文中以一個(gè)著色游戲?yàn)槔v解了邊界的填充等各種填色操作,需要的朋友可以參考下2016-02-02
android開(kāi)發(fā)教程之實(shí)現(xiàn)滑動(dòng)關(guān)閉fragment示例
這篇文章主要介紹了android實(shí)現(xiàn)滑動(dòng)關(guān)閉fragment示例,需要的朋友可以參考下2014-03-03
Android實(shí)現(xiàn)網(wǎng)易Tab分類排序控件實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了Android仿網(wǎng)易Tab分類排序控件的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03
Android DrawLayout結(jié)合ListView用法實(shí)例
這篇文章主要介紹了Android DrawLayout結(jié)合ListView用法實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
Android多種方式實(shí)現(xiàn)相機(jī)圓形預(yù)覽的示例代碼
這篇文章主要介紹了Android多種方式實(shí)現(xiàn)相機(jī)圓形預(yù)覽的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
Android 開(kāi)發(fā)中l(wèi)ayout下的子文件夾
這篇文章主要介紹了android 開(kāi)發(fā)中l(wèi)ayout下的子文件夾,需要的朋友可以參考下2017-12-12
詳解Android Studio無(wú)法檢測(cè)新版本問(wèn)題解決
這篇文章主要介紹了詳解Android Studio無(wú)法檢測(cè)新版本問(wèn)題解決,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08

