1发绢、前言
很多電商類app的商品詳情頁(yè)(其他app也有類似效果好港,比如qq音樂(lè))都有這么一個(gè)效果:當(dāng)用戶向上滑動(dòng)頁(yè)面內(nèi)容超過(guò)一定距離后媒怯,中間的標(biāo)題欄會(huì)卡在頂部,往下拉回到固定高度后又會(huì)消失纵装。效果圖如下:
這效果用戶體驗(yàn)還是很酷炫征讲,今天我們就來(lái)講解如何實(shí)現(xiàn)這個(gè)效果。
2橡娄、分析
為了方便理解诗箍,作圖分析
如圖所示,整個(gè)頁(yè)面分為四個(gè)部分:
- 懸浮內(nèi)容,floatView
- 頂部?jī)?nèi)容,headView
- 中間內(nèi)容,與懸浮內(nèi)容相同,middleView
- 商品詳情展示頁(yè)面,detailView
因?yàn)轫?yè)面內(nèi)容高度會(huì)超出屏幕挽唉,所以用Scrollview實(shí)現(xiàn)滾動(dòng)滤祖,懸浮view與scrollview同級(jí)筷狼,都在一個(gè)幀布局或者相對(duì)布局中。
當(dāng)y方向的滾動(dòng)距離小于中間的內(nèi)容middleView
到頂部的距離時(shí)匠童,middleView理所當(dāng)然的會(huì)隨這頁(yè)面向上滑動(dòng)而消失埂材,我們顯示懸浮view,從而實(shí)現(xiàn)middleView一直卡在頂部的效果俏让。
當(dāng)y方向滾動(dòng)距離大于中間的內(nèi)容middleView
容到頂部的距離時(shí)楞遏,懸浮view隱藏即可茬暇。
通過(guò)分析首昔,我們發(fā)現(xiàn)只要知道scrollview
的滾動(dòng)距離和middleView
到頂部的高度即可。至此將復(fù)雜的交互特效變成了倆個(gè)簡(jiǎn)單的api糙俗。
3勒奇、第一種方法實(shí)現(xiàn)
3.1 獲取middleView
的到父容器頂部的距離
tv_title.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
{
@Override
public void onGlobalLayout()
{
mTitleTopAndHeight = tv_title.getTop();
tv_title.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
在activity的oncreate()
中直接通過(guò)view的getTop()
方法獲取到view的高度會(huì)返回0,這是因?yàn)榇藭r(shí)view還沒(méi)有繪制到界面巧骚,所以我們采用上面的方法給view設(shè)置監(jiān)聽(tīng)獲取赊颠,由于可能發(fā)生多次繪制,所以最后記得移除監(jiān)聽(tīng)事件劈彪。
以下代碼同樣可以獲瓤⒈摹:
tv_title.post(new Runnable()
{
@Override
public void run()
{
mTitleTopAndHeight = tv_title.getTop();
}
});
利用post方法將操作放到隊(duì)列中,等系統(tǒng)布局完成后執(zhí)行隊(duì)列中的事件沧奴,同樣可以獲取到正確的view的top
值痘括。
3.2 獲取垂直方向滾動(dòng)距離
在Scrollview的父類View
中有個(gè)內(nèi)容變化的方法onScrollChanged()
,雖然該方法是protect的外部不可調(diào)用,但是在內(nèi)部滔吠,當(dāng)scrollview
滾動(dòng)時(shí)就會(huì)執(zhí)行該方法纲菌,所以我們自定義一個(gè)MyScrollView
在onScrollChanged()
通過(guò)回調(diào)將滾動(dòng)的距離傳遞給外部。
自定義scrollview完整代碼如下:
public class MyScrollView extends ScrollView
{
private OnScrollListener mOnScrollListener;
/**
* 是否用戶手指觸摸滑動(dòng)
*/
private boolean mIsTouch = false;
public MyScrollView(Context context)
{
this(context, null);
}
public MyScrollView(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt)
{
super.onScrollChanged(l, t, oldl, oldt);
if (mOnScrollListener != null)
{
mOnScrollListener.onScroll(t, mIsTouch ? OnScrollListener.SCROLL_STATE_TOUCH_SCROLL : OnScrollListener.SCROLL_STATE_FLING);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev)
{
switch (ev.getAction())
{
case MotionEvent.ACTION_MOVE:
mIsTouch = true;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mIsTouch = false;
break;
}
return super.onTouchEvent(ev);
}
public void setOnScrollListener(OnScrollListener onScrollListener)
{
mOnScrollListener = onScrollListener;
}
public interface OnScrollListener
{
/**
* 用戶手指拖動(dòng)滾動(dòng)
*/
int SCROLL_STATE_TOUCH_SCROLL = 0x0;
/**
* 慣性滑行滾動(dòng)
*/
int SCROLL_STATE_FLING = 0x1;
/**
* 滾動(dòng)時(shí)的回調(diào)
*
* @param scrollY Y方向滾動(dòng)的距離
* @param scroll_state 當(dāng)前滾動(dòng)狀態(tài):自由滾動(dòng)或者手勢(shì)拖動(dòng)滾動(dòng)
*/
void onScroll(int scrollY, int scroll_state);
}
}
3.3 使用
在acitivity中給scrollview設(shè)置自定義滾動(dòng)監(jiān)聽(tīng)事件即可
mScrollView.setOnScrollListener(new MyScrollView.OnScrollListener()
{
@Override
public void onScroll(int scrollY, int state)
{
Log.d("onScroll: ", scrollY + "" + "----------- state:" + state);
if (scrollY <= mTitleTopAndHeight)
{
tv_float.setVisibility(View.INVISIBLE);
} else
{
tv_float.setVisibility(View.VISIBLE);
}
}
});
這樣疮绷,通過(guò)垂直方法的滾動(dòng)值來(lái)控制floatView的顯示隱藏翰舌,
tv_float.setOnTouchListener(new View.OnTouchListener()
{
@Override
public boolean onTouch(View v, MotionEvent event)
{
mScrollView.onTouchEvent(event);
return false;
}
});
給懸浮view設(shè)置觸摸監(jiān)聽(tīng),將用戶手勢(shì)傳遞給scrollView冬骚,這樣用戶滑動(dòng)懸浮view時(shí)椅贱,內(nèi)容區(qū)域也可以跟隨滾動(dòng)。
下面是布局代碼只冻,文章最后會(huì)附上demo下載地址:
<?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"
>
<com.example.qike.scrolltitle.MyScrollView
android:id="@+id/sv_main"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:gravity="center"
android:text="商品圖片"/>
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="#a3c"
android:gravity="center"
android:text="標(biāo)題view"/>
<TextView
android:layout_width="match_parent"
android:layout_height="600dp"
android:background="#a2bb"
android:gravity="center"
android:text="詳情頁(yè)面"/>
</LinearLayout>
</com.example.qike.scrolltitle.MyScrollView>
<TextView
android:id="@+id/tv_float"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="#a3c"
android:gravity="center"
android:text="標(biāo)題view"
android:visibility="invisible"/>
</RelativeLayout>
4庇麦、第二種方式
本方法與第一種方式的區(qū)別就是獲取滾動(dòng)位置的方法不同,該方法更簡(jiǎn)單一些:
mScrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener()
{
@Override
public void onScrollChanged()
{
int scrollY = mScrollView.getScrollY();
if (scrollY <= mTitleTopAndHeight)
{
tv_float.setVisibility(View.INVISIBLE);
} else
{
tv_float.setVisibility(View.VISIBLE);
}
}
});
可能有讀者要問(wèn)属愤,既然有這種簡(jiǎn)單的方法直接設(shè)置監(jiān)聽(tīng)女器,為什么還介紹第一種方法。細(xì)心的你可能已經(jīng)發(fā)現(xiàn)住诸,我在第一種方法自定義的監(jiān)聽(tīng)事件中驾胆,把表示當(dāng)前滾動(dòng)狀態(tài)的參數(shù)statue
回調(diào)給了監(jiān)聽(tīng)方涣澡,因?yàn)楹芏郺pp在用戶手動(dòng)拖動(dòng)滾動(dòng)與慣性滾動(dòng)的處理是不同的。比如淘寶商品詳情頁(yè)面丧诺,當(dāng)達(dá)到邊界值中間view的top值
時(shí)入桂,只有用戶手動(dòng)拖動(dòng)一段距離后才會(huì)拉出底部的詳情類容,而慣性滑動(dòng)的話只會(huì)停在那里驳阎。
5抗愁、結(jié)束
到此,關(guān)于如果實(shí)現(xiàn)按鈕隨著上下滾動(dòng)而懸浮頂在固定位置的方法就講完了呵晚。
下面是demo下載地址.