Android模仿知乎的回答詳情頁(yè)的動(dòng)畫(huà)效果
廢話不多說(shuō),咱們第一篇文章就是模仿“知乎”的回答詳情頁(yè)的動(dòng)畫(huà)效果,先上個(gè)原版的效果圖,咱們就是要做出這個(gè)效果

在實(shí)現(xiàn)之前,我們先根據(jù)上面的動(dòng)畫(huà)效果,研究下需求,因?yàn)間if幀數(shù)有限,所以不是很連貫,推薦你直接下載一個(gè)知乎,找到這個(gè)界面自己玩玩
☞當(dāng)文章往上移動(dòng)到一定位置之后,最上面的標(biāo)題欄Bar和問(wèn)題布局Title是會(huì)隱藏的,回答者Author布局不會(huì)隱藏
☞當(dāng)文章往下移動(dòng)移動(dòng)到一定位置之后,原先隱藏的標(biāo)題欄Bar和問(wèn)題布局Title會(huì)下降顯示
☞當(dāng)文章往上移動(dòng)的時(shí)候,下部隱藏的Tools布局會(huì)上升顯示
☞當(dāng)文章往下移動(dòng)的時(shí)候,如果Tools布局是顯示的,則隱藏
☞當(dāng)標(biāo)題欄Bar和問(wèn)題布局Title下降顯示的時(shí)候,Title是從Bar的下面出來(lái)的,有個(gè)遮擋的效果
☞當(dāng)快速滑動(dòng)內(nèi)容到達(dá)底部的時(shí)候,隱藏的Tools會(huì)顯示出來(lái)
☞當(dāng)快速滑動(dòng)內(nèi)容到頂部的時(shí)候,隱藏的Bar和Title也會(huì)顯示出來(lái)
不分析不知道,這樣一個(gè)簡(jiǎn)單地效果,經(jīng)過(guò)分析需要完成不少東西呢,那么下面根據(jù)要實(shí)現(xiàn)的需求,咱們分析一下解決方案。
在做這種仿界面之前,我們可以使用ADT帶的View Hierarchy工具看一下“知乎”原生是怎么實(shí)現(xiàn)的

從右邊的分析圖可以看出,知乎的這個(gè)界面,內(nèi)容用的WebView,這很正常,因?yàn)橛脩舻幕卮鹄锩娓袷奖容^復(fù)雜,用WebView是最好的解決方案,而標(biāo)題欄是一個(gè)VIew,是ActionBar還是自定義View呢,不得而知,下面是就是一個(gè)LinearLayout包了4個(gè)ToggleButton,布局很簡(jiǎn)單,我們沒(méi)有WebView,所以使用ScrollView代替,上面的布局直接ImageView了,設(shè)置個(gè)src,模擬一個(gè)布局。
其實(shí)布局很簡(jiǎn)單,咱們一個(gè)效果一個(gè)效果的來(lái)實(shí)現(xiàn)。
首先是下面的Tools如何顯示和隱藏呢?當(dāng)然是用動(dòng)畫(huà)了!什么動(dòng)畫(huà)呢?能實(shí)現(xiàn)的有屬性動(dòng)畫(huà)和幀動(dòng)畫(huà),屬性動(dòng)畫(huà)能夠真實(shí)的改變View的屬性,幀動(dòng)畫(huà)只是視覺(jué)上移動(dòng)了,View的實(shí)際屬性并不改變,這兩種都可以,我們這里使用屬性動(dòng)畫(huà)
/**
* 顯示工具欄
*/
private void showTools() {
ObjectAnimator anim = ObjectAnimator.ofFloat(img_tools, "y", img_tools.getY(),
img_tools.getY() - img_tools.getHeight());
anim.setDuration(TIME_ANIMATION);
anim.start();
isToolsHide = false;
}
/**
* 隱藏工具欄
*/
private void hideTools() {
ObjectAnimator anim = ObjectAnimator.ofFloat(img_tools, "y", img_tools.getY(),
img_tools.getY() + img_tools.getHeight());
anim.setDuration(TIME_ANIMATION);
anim.start();
isToolsHide = true;
}
那么什么時(shí)候調(diào)用呢?從上面的需求分析中,我們知道,用戶手指下拉的時(shí)候,Tools顯示,反之隱藏,那么我們就可以監(jiān)聽(tīng)ScrollView的onTouch,判斷手指方向,實(shí)現(xiàn)動(dòng)畫(huà)效果的調(diào)用
mScroller.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float disY = event.getY() - lastY;
//垂直方向滑動(dòng)
if (Math.abs(disY) > viewSlop) {
//是否向上滑動(dòng)
isUpSlide = disY < 0;
//實(shí)現(xiàn)底部tools的顯示與隱藏
if (isUpSlide) {
if (!isToolsHide)
hideTools();
} else {
if (isToolsHide)
showTools();
}
}
break;
}
return false;
}
});
用變量isToolsHide放置代碼重復(fù)調(diào)用。
下面的Tools的問(wèn)題解決了,我們?cè)倏匆幌律厦娴牟季謩?dòng)畫(huà)如何來(lái)實(shí)現(xiàn)。上面的思路和下面一樣,也是通過(guò)屬性動(dòng)畫(huà),完成位置的移動(dòng),移動(dòng)的布局有Bar、Title和根布局。為什么答題人布局Author不移動(dòng)呢?因?yàn)楦季直仨氁苿?dòng),否則就會(huì)擋住下面的文字內(nèi)容,根布局的移動(dòng)會(huì)讓子布局都跟著移動(dòng),所以只移動(dòng)根布局即可。
對(duì)了,為了更大范圍的現(xiàn)實(shí)文本,“知乎”的WebView是占據(jù)整個(gè)布局的,其他布局都是在根布局FrameLayout里面,所以,在首次加載的時(shí)候,下面的文本在開(kāi)頭需要留出一定的間隔,防止被遮擋,當(dāng)上面的布局隱藏之后,就沒(méi)有問(wèn)題了。
在簡(jiǎn)單分析之后,我再給出實(shí)現(xiàn)的布局的代碼
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
>
<com.socks.zhihudetail.MyScrollView
android:id="@+id/scroller"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="16sp"
android:textColor="@android:color/black"
android:text="@string/hello_world"/>
</com.socks.zhihudetail.MyScrollView>
<FrameLayout
android:id="@+id/ll_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:orientation="vertical"
android:layout_gravity="top">
<ImageView
android:id="@+id/img_author"
android:layout_width="match_parent"
android:layout_height="80dp"
android:scaleType="fitXY"
android:src="@drawable/bg_author"/>
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="55dp"
android:text="為什么美國(guó)有那么多肌肉極其強(qiáng)大的肌肉男?"
android:textSize="18sp"
android:background="#DBDBDB"
android:gravity="center|left"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:textColor="@android:color/darker_gray"
/>
<ImageView
android:id="@+id/img_bar"
android:layout_width="match_parent"
android:layout_height="55dp"
android:scaleType="fitXY"
android:src="@drawable/bg_actionbar"/>
</FrameLayout>
<ImageView
android:id="@+id/img_tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="fitXY"
android:layout_gravity="bottom"
android:src="@drawable/bg_bottom"/>
</FrameLayout>
效果圖如下,文本留了一些空行,保證不被遮擋。

有的同學(xué)看了上面的效果圖可能會(huì)疑惑,這里為什么沒(méi)有答題人的布局呢?
其實(shí)是這樣的,為了模擬上部的布局顯示時(shí),Title從Bar下面出現(xiàn)的效果,所以特意這樣設(shè)計(jì)的。我試過(guò)用linearLayout實(shí)現(xiàn),效果也是可以實(shí)現(xiàn)的,但是當(dāng)Title往下移動(dòng)顯示的時(shí)候,會(huì)覆蓋在Bar上面,這也很好理解,LinearLayout沒(méi)有層次順序,所以會(huì)遮擋。我試過(guò)View.bringToFront(),試圖把Bar的布局提高層次,但是這樣會(huì)導(dǎo)致布局的紊亂,在首次加載的時(shí)候,Bar會(huì)顯示在最下面,是因?yàn)樘岣邔哟沃螅珺ar的布局重新計(jì)算,所以不按照LinearLayout的布局規(guī)則來(lái)了。無(wú)奈之下,換成了Framelayout,但是又出現(xiàn)了問(wèn)題,Bar的高度可以設(shè)置,但是Title的高度會(huì)隨著文本的增加而改變,這樣一來(lái),最下面Author的布局的位置就不能設(shè)置了,因?yàn)椴恢谰嚯x上面多遠(yuǎn),所以我們只能在代碼里面動(dòng)態(tài)的計(jì)算Bar和Title的高度,然后在界面加載的時(shí)候,動(dòng)態(tài)的給Author的布局設(shè)置MargenTop,保證位置的正確。
因?yàn)樵趏nCreate里面,還沒(méi)有開(kāi)始View的繪制,所以得不到控件的真實(shí)高度,我們可以用下面的方法,獲取這個(gè)時(shí)期的高度
//獲取Bar和Title的高度,完成auther布局的margenTop設(shè)置
ViewTreeObserver viewTreeObserver = fl_top.getViewTreeObserver();
viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (!hasMeasured) {
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(FrameLayout
.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT);
layoutParams.setMargins(0, img_bar.getHeight() + tv_title.getHeight(), 0, 0);
img_author.setLayoutParams(layoutParams);
hasMeasured = true;
}
return true;
}
});
獲取了高度之后,我們就可以正確地設(shè)置位置了。但是,如果保證上面的布局隨著我們的內(nèi)容的移動(dòng),而改變現(xiàn)實(shí)狀態(tài)呢?
經(jīng)過(guò)我手動(dòng)直觀測(cè)試,知乎的這個(gè)界面是根據(jù)一個(gè)固定的值,來(lái)改變顯示狀態(tài)的,因此,我們可以監(jiān)聽(tīng)ScrollView的滑動(dòng)距離,來(lái)判斷。但是ScrollView并沒(méi)有給我們這樣一個(gè)監(jiān)聽(tīng)器,咋辦?重寫(xiě)!
/**
* Created by zhaokaiqiang on 15/2/26.
*/
public class MyScrollView extends ScrollView {
private BottomListener bottomListener;
private onScrollListener scrollListener;
public MyScrollView(Context context) {
this(context, null);
}
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (getScrollY() + getHeight() >= computeVerticalScrollRange()) {
if (null != bottomListener) {
bottomListener.onBottom();
}
}
if (null != scrollListener) {
scrollListener.onScrollChanged(l, t, oldl, oldt);
}
}
public void setBottomListener(BottomListener bottomListener) {
this.bottomListener = bottomListener;
}
public void setScrollListener(onScrollListener scrollListener) {
this.scrollListener = scrollListener;
}
public interface onScrollListener {
public void onScrollChanged(int l, int t, int oldl, int oldt);
}
public interface BottomListener {
public void onBottom();
}
}
我們只需要重寫(xiě)onScrollChange()方法即可,在里面不光可以時(shí)時(shí)的得到位置的變化,還添加了一個(gè)BottomListener接口來(lái)監(jiān)聽(tīng)滑動(dòng)到底部的事件,寫(xiě)好之后就很簡(jiǎn)單了
mScroller.setBottomListener(this); mScroller.setScrollListener(this);
/**
* 顯示上部的布局
*/
private void showTop() {
ObjectAnimator anim1 = ObjectAnimator.ofFloat(img_bar, "y", img_bar.getY(),
0);
anim1.setDuration(TIME_ANIMATION);
anim1.start();
ObjectAnimator anim2 = ObjectAnimator.ofFloat(tv_title, "y", tv_title.getY(),
img_bar.getHeight());
anim2.setInterpolator(new DecelerateInterpolator());
anim2.setDuration(TIME_ANIMATION + 200);
anim2.start();
ObjectAnimator anim4 = ObjectAnimator.ofFloat(fl_top, "y", fl_top.getY(),
0);
anim4.setDuration(TIME_ANIMATION);
anim4.start();
isTopHide = false;
}
/**
* 隱藏上部的布局
*/
private void hideTop() {
ObjectAnimator anim1 = ObjectAnimator.ofFloat(img_bar, "y", 0,
-img_bar.getHeight());
anim1.setDuration(TIME_ANIMATION);
anim1.start();
ObjectAnimator anim2 = ObjectAnimator.ofFloat(tv_title, "y", tv_title.getY(),
-tv_title.getHeight());
anim2.setDuration(TIME_ANIMATION);
anim2.start();
ObjectAnimator anim4 = ObjectAnimator.ofFloat(fl_top, "y", 0,
-(img_bar.getHeight() + tv_title.getHeight()));
anim4.setDuration(TIME_ANIMATION);
anim4.start();
isTopHide = true;
}
@Override
public void onBottom() {
if (isToolsHide) {
showTools();
}
}
@Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {
if (t <= dp2px(TOP_DISTANCE_Y) && isTopHide && isAnimationFinish) {
showTop();
Log.d(TAG, "顯示");
} else if (t > dp2px(TOP_DISTANCE_Y) && !isTopHide && isAnimationFinish) {
hideTop();
Log.d(TAG, "隱藏");
}
}
我們只需要根據(jù)當(dāng)前的位置,來(lái)實(shí)現(xiàn)布局的顯示和隱藏就可以啦!
OK,這篇文章就到這里,希望對(duì)大家的學(xué)習(xí)有所幫助。
相關(guān)文章
Android 開(kāi)發(fā)使用Activity實(shí)現(xiàn)加載等待界面功能示例
這篇文章主要介紹了Android 開(kāi)發(fā)使用Activity實(shí)現(xiàn)加載等待界面功能,結(jié)合實(shí)例形式詳細(xì)分析了Android基于Activity實(shí)現(xiàn)加載等待界面布局與功能操作技巧,需要的朋友可以參考下2020-05-05
android Retrofit2+okHttp3使用總結(jié)
本篇文章主要介紹了android Retrofit2+okHttp3使用總結(jié),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04
Android應(yīng)用內(nèi)懸浮窗的實(shí)現(xiàn)方案示例
本篇文章主要介紹了Android應(yīng)用內(nèi)懸浮窗的實(shí)現(xiàn)方案示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08
android 網(wǎng)絡(luò)編程之網(wǎng)絡(luò)通信幾種方式實(shí)例分享
這篇文章主要介紹了android 網(wǎng)絡(luò)編程之網(wǎng)絡(luò)通信幾種方式,有需要的朋友可以參考一下2013-12-12
android?WindowManager的簡(jiǎn)單使用實(shí)例詳解
這篇文章主要介紹了android?WindowManager的簡(jiǎn)單使用,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08
Android動(dòng)態(tài)更新Menu菜單的實(shí)現(xiàn)過(guò)程
菜單是用戶界面中最常見(jiàn)的元素之一,使用非常頻繁,下面這篇文章主要給大家介紹了關(guān)于Android動(dòng)態(tài)更新Menu菜單的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09
android如何添加桌面圖標(biāo)和卸載程序后自動(dòng)刪除圖標(biāo)
android如何添加桌面圖標(biāo)和卸載程序后自動(dòng)刪除桌面圖標(biāo),這是一個(gè)應(yīng)用的安裝與卸載過(guò)程對(duì)桌面圖標(biāo)的操作,下面與大家分享下具體是如何實(shí)現(xiàn)的,感興趣的朋友可以參考下哈2013-06-06
Android 后臺(tái)發(fā)送郵件示例 (收集應(yīng)用異常信息+Demo代碼)
今天介紹個(gè)更簡(jiǎn)單的方法,我們把異常信息收集后,通過(guò)后臺(tái)發(fā)送郵件方法,把相關(guān)異常信息發(fā)送到我們指定的郵箱里面2013-07-07
Android自定義RecyclerView Item頭部懸浮吸頂
這篇文章主要為大家詳細(xì)介紹了Android自定義RecyclerView Item頭部懸浮吸頂,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08

