本博客由gengqiquan原創,轉載請註明出處http://blog.csdn.net/gengqiquan/article/details/50542334尊重他人的技術勞動成果,謝謝
UI妹子是個喜歡看各種軟件的妹子,這不,最近看上了UC的首頁,於是把我們的應用也改成了這樣的風格,本着不重複造輪子的原則,網上百度了一大堆,全特麼沒有用。好吧,不重複造輪子不代表自己不去製造輪子,現在我們來自己造一個。
先看效果(不貼效果寡代碼的人太可惡了,複製粘貼後才發現不是自己要的,簡直浪費寶貴時間)
圖片有點小,沒辦法,CSDN最大隻能傳2mb,隨便轉換下都不止,只好用小圖了。
效果應該還可以,要實現上面的效果,我們需要做哪些事呢?
首先,我們得有一個容器,由佈局可知容器最好是線性的,這裏我們用LinearLayout,然後我們是滑動隱藏,要讓LinearLayout有平滑的滑動效果,我們就要用到Scroller,不清楚Scroller的同學可以自行百度,我這裏就不貼鏈接了,網上一大堆。滑動的代碼基本都這樣
private void smoothScrollTo(int dx, int dy) {
mScroller.startScroll(getScrollX(), getScrollY(), dx, dy - getScrollY());
invalidate();
if (dy == 0) {//這裏可以判斷滑動完成後頂部是隱藏還是現實,在回調中可以控制界面的底部的展現和隱藏,比如改變tab的透明度什麼的
mShowing = true;
if (onHeaderScrollLiesten != null)
onHeaderScrollLiesten.isCompute(true);
} else if (dy == Max) {
if (onHeaderScrollLiesten != null)
onHeaderScrollLiesten.isCompute(false);
mShowing = false;
}
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
恩,然後我們是滑動隱藏,必然涉及到觸摸事件,這裏我們重寫OnTouchEvent()函數來實現滑動
@Override
public boolean onTouchEvent(MotionEvent ev) {
int scrollY = getScrollY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
oldY = ev.getY();
mDLastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
int mSlid = (int) (ev.getY() - mDLastY);
if (mSlid < 0 && scrollY < Max) {
if (onHeaderScrollLiesten != null)
onHeaderScrollLiesten.onScoll(-mSlid, Max);
scrollBy(0, -mSlid);
}
break;
case MotionEvent.ACTION_UP:
if (scrollY > 0)
if (Math.abs(scrollY) < Max / 2) {
smoothScrollTo(0, 0);
} else {
smoothScrollTo(0, Max);
}
break;
}
mDLastY = ev.getY();
return true;
}
這裏沒有註釋,稍微講解下, 我們在MotionEvent.ACTION_DOWN裏獲取手指的按下位置,然後再在ACTION_MOVE裏用當前的手指Y座標與之前的Y座標作比較,當他在0-Max之間時響應滑動,這裏的Max是頂部待隱藏的佈局的高度,值的獲取下面我們再講,最後我們在ACTION_UP裏判斷滑動的距離scrollY,這個值是有正有負的。代表往不同的方向滑動,在有滑動的情況下我們與Max的一半高度作比較,大於他我們就隱藏,小於就恢復到初始狀態,最後的最後不要忘了mDLastY = ev.getY();這一句,記錄下當前的Y座標作爲下一次進入onTouchEvent()時的上一次Y座標記錄。
經過以上代碼。我們實現瞭如何使一個LinearLayout可以響應手指進行滑動,但我們上面的效果其實不僅僅是滑動隱藏,還要響應下面的tab選項卡的水平滑動,並且當頂部隱藏時可以單個列表可以下拉刷新,那種一次下拉刷新所有列表的坑用戶流量的反人類行爲我們是不做的,那麼如何做到只響應上滑隱藏的事件的同時響應底部各種的滑動切換和刷新事件呢?
這裏我們決定使用事件分發機制裏的第二種方式,父容器攔截法,我們來重寫onInterceptTouchEvent()方法。代碼如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean indelet = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
indelet = false;
if (!mScroller.isFinished()) {// 保證停止動畫防止衝突
mScroller.abortAnimation();
indelet = true;
}
break;
case MotionEvent.ACTION_MOVE:
float dx = ev.getX() - mlastinterceptx;
float dy = ev.getY() - mlastinterceptY;
// 橫向滑動
if (Math.abs(dx) >= Math.abs(dy)) {
indelet = false;
} else {// 豎向滑動
if (mShowing) {
if (dy < 0)// 這裏我們只需要響應豎向滑動的上滑;
indelet = true;
} else {
indelet = false;
}
}
break;
case MotionEvent.ACTION_UP:
indelet = false;
break;
}
mlastinterceptx = (int) ev.getX();
mlastinterceptY = (int) ev.getY();
mDLastY = ev.getY();
return indelet;
}
核心代碼就在ACTION_MOVE時
if (Math.abs(dx) >= Math.abs(dy)) {// 橫向滑動
indelet = false;
} else {// 豎向滑動
if (mShowing) {
if (dy < 0)// 這裏我們只需要響應豎向滑動的上滑;
indelet = true;
} else {
indelet = false;
}
}
我們只要響應向上滑動就可以了,所以遇到橫向滑動時直接傳遞給子控件,然後當爲上下滑動時,我們判斷滑動值,小於0時爲向上,我們截取這個時候的觸摸事件。
千萬記住ACTION_DOWN的時候如果非在動畫進行時一定要返回false,否則下面的所有事件直接都由當前父容器處理,內部的控件接受不到任何觸摸事件了。
最後我們來看下如何測量頂部佈局高度Max。
很簡單,重寫onMeasure(),就好,第一次進入onMeasure()時獲取到頂部的高度,當然我們也可以寫個public方法來在外部設置Max的大小,不過我懶,而且我也不知道以後接手這個項目的人看了這個控件是否還記得調用這個方法,到時候沒效果豈不是要噴死我。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!isMeasured) {
isMeasured = true;
topView = getChildAt(0);
bottomView = getChildAt(1);
Max = topView.getMeasuredHeight();
}
bottomView.measure(widthMeasureSpec, heightMeasureSpec);
}
以上就是整個效果的完整實現流程了,貼一下完整的代碼
控件類的代碼
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.Scroller;
import com.umeng.socialize.utils.Log;
// TODO: Auto-generated Javadoc
/**
*
*
* @author gengqiquan
* @version v1.0
* @date:2016年1月19日13:48:13
*/
public class UpHideScrollView extends LinearLayout {
int Max = 0;//頂部待隱藏的高度
private boolean isMeasured = false;//是否第一次測量大小
private View topView, bottomView;
public boolean mShowing = true;
float oldY, mDLastY;
Scroller mScroller;
int mlastinterceptx, mlastinterceptY;
OnHeaderScrollLiesten onHeaderScrollLiesten;
public UpHideScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
setScrollBarStyle(SCROLL_AXIS_NONE);
mScroller = new Scroller(context);
}
private void smoothScrollTo(int dx, int dy) {
mScroller.startScroll(getScrollX(), getScrollY(), dx, dy - getScrollY());
invalidate();
if (dy == 0) {//這裏可以判斷滑動完成後頂部是隱藏還是現實,在回調中可以控制界面的底部的展現和隱藏,比如改變tab的透明度什麼的
mShowing = true;
if (onHeaderScrollLiesten != null)
onHeaderScrollLiesten.isCompute(true);
} else if (dy == Max) {
if (onHeaderScrollLiesten != null)
onHeaderScrollLiesten.isCompute(false);
mShowing = false;
}
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean indelet = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
indelet = false;
if (!mScroller.isFinished()) {// 保證停止動畫防止衝突
mScroller.abortAnimation();
indelet = true;
}
break;
case MotionEvent.ACTION_MOVE:
float dx = ev.getX() - mlastinterceptx;
float dy = ev.getY() - mlastinterceptY;
// 橫向滑動
if (Math.abs(dx) >= Math.abs(dy)) {
indelet = false;
} else {// 豎向滑動
if (mShowing) {
if (dy < 0)// 這裏我們只需要響應豎向滑動的上滑;
indelet = true;
} else {
indelet = false;
}
}
break;
case MotionEvent.ACTION_UP:
indelet = false;
break;
}
mlastinterceptx = (int) ev.getX();
mlastinterceptY = (int) ev.getY();
mDLastY = ev.getY();
return indelet;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int scrollY = getScrollY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
oldY = ev.getY();
mDLastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
int mSlid = (int) (ev.getY() - mDLastY);
if (mSlid < 0 && scrollY < Max) {
if (onHeaderScrollLiesten != null)
onHeaderScrollLiesten.onScoll(-mSlid, Max);
scrollBy(0, -mSlid);
}
break;
case MotionEvent.ACTION_UP:
if (scrollY > 0)
if (Math.abs(scrollY) < Max / 2) {
smoothScrollTo(0, 0);
} else {
smoothScrollTo(0, Max);
}
break;
}
mDLastY = ev.getY();
return true;
}
float firstY,sendLastY;
public void sendEvent(MotionEvent ev) {
int scrollY =(int) (ev.getY() - firstY);
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
firstY = ev.getY();
sendLastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
if (!mShowing) {
int mSlid = (int) (ev.getY() - sendLastY);
if (mSlid >0 && scrollY < Max&&mSlid<Max) {
if (onHeaderScrollLiesten != null)
onHeaderScrollLiesten.onScoll(-mSlid, Max);
scrollBy(0, -mSlid);
}
}
break;
case MotionEvent.ACTION_UP:
if (scrollY > 0) {
if (Math.abs(scrollY) < Max / 2) {
smoothScrollTo(0, Max);
} else {
smoothScrollTo(0, 0);
}
}
break;
}
sendLastY = ev.getY();
}
public void setOnHeaderScrollLiesten(OnHeaderScrollLiesten liesten) {
onHeaderScrollLiesten = liesten;
}
public interface OnHeaderScrollLiesten {
void onScoll(int y, int h);
void isCompute(boolean b);//
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
super.dispatchTouchEvent(ev);
return true;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b + Max);
}
int topHeight;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!isMeasured) {
isMeasured = true;
topView = getChildAt(0);
bottomView = getChildAt(1);
Max = topView.getMeasuredHeight();
}
bottomView.measure(widthMeasureSpec, heightMeasureSpec);
}
public void ShowHeader() {
smoothScrollTo(0, 0);
}
public void ShowHeaderDelay() {
mScroller.startScroll(getScrollX(), getScrollY(), 0, 0 - getScrollY(), 2000);
invalidate();
mShowing = true;
}
public void HideHeader() {
smoothScrollTo(0, Max);
}
}
activity佈局的代碼
<com.qbs.itrytryc.views.UpHideScrollView
android:id="@+id/uphide"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.qbs.itrytryc.views.TurnView
android:id="@+id/turnView"
android:layout_width="match_parent"
android:layout_height="168dp" />
<com.qbs.itrytryc.views.SlidingFixTabView
android:id="@+id/slid"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.qbs.itrytryc.views.UpHideScrollView>
activity中調用的代碼
hideScrollView.setOnHeaderScrollLiesten(new OnHeaderScrollLiesten() {
@Override
public void onScoll(int y, int h) {
}
@Override
public void isCompute(boolean Showing) {
if (!Showing) {//這裏通知界面做一些響應隱藏的事件,比如隱藏底部tab
EventBus.getDefault().post(new IntEvent());
}
}
});
}
直接調用代碼顯示頭部
upHideScrollView.ShowHeaderDelay();
監聽listview的滾動自動顯示頭部
listView.setOnTouchListener(new View.OnTouchListener() {
float mDLastY;
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getY() - mDLastY > 0) {
if (listView.getFirstVisiblePosition() == 0)
upHideScrollView.sendEvent(event);
}
return false;
}
});
好了,祝大家新年快樂。
補充更新:一直有人找我要源碼,其實這篇博客裏的代碼就是源碼。已經很全了,有些人估計想要的是能運行的demo。問題這是我很久前的公司項目。項目代碼已經交接了,我是不會保存的。湊巧上個月新公司也有個類似的需求效果,於是我再次寫了個完整的控件,並且增加了下滑顯示功能。這次上傳到資源庫方便那些實在懶得要死的人,不過收5分
上滑隱藏下滑顯示控件
最後再重申一遍,博客代碼就是源碼
我建了一個QQ羣(羣號:121606151),用於大家討論交流Android技術問題,有興趣的可以加下,大家一起進步。