聲明:本篇文章已授權微信公衆號 YYGeeker 獨家發佈
前言
最近在淘寶上囤年貨買買買的時候,注意到淘寶購物詳情頁的漸變效果,覺得效果還是挺不錯的,有種似曾相識的感覺,沒錯,好像QQ空間的標題欄也是類似的做法。鑑於這種效果在平時可能會用得比較多,所以就自己研究了一下然後把它實現出來了,項目完整demo在文章底部有鏈接,大家可下載參考研究。文章原創,轉載請註明地址:小嵩的CSDN博客,地址:http://blog.csdn.net/qq_22393017
正文
按照慣例,先上一張要實現的效果圖吧,有興趣的小夥伴可以繼續往下看。
淘寶APP的實際效果:
自己實現的效果:
(文章末尾有demo下載地址,可下載demo查看完整源代碼)
主要功能點
本文會詳細地講解這個功能的實現思路,主要有以下四個功能點:
- 滑動時標題欄透明度漸變。
- 標題欄的幾個ICON控件在滑動時背景漸變
- 在標題欄有透明度時,ICON是白色的;標題欄變成不透明時,ICON替換成灰色。
- 頭部圖片滑動視差效果。
以上這四個功能點都是通過手指的滑動距離來進行處理的。那麼首先,我們就需要監聽獲取滑動距離,那麼怎樣獲取這個滑動距離並進行處理呢?不急,咱們慢慢思考,一步一步來。淘寶這個頁面的內容部分,應該是用的ScrollView,而原生的ScrollView,它沒有提供onScrollChanged的回調方法。所以我們有兩種方式:
- 一種是通過自定義View,讓其包含ScrollView子控件。
- 一種是自定義一個ScrollView,重寫onScrollChanged方法。
第一種方式可以參考Android–仿淘寶商品詳情(繼續拖動查看詳情)及標題欄漸變 這篇博客,這篇博客由於寫了Scrollview滑動到底部,繼續拖動查看詳情的動能,所以我就不重複寫了,可參考它的實現方式。
這篇文章我們換第二種方式,即自定義Scrollview來實現標題欄漸變。onScrollChanged(int x, int y, int oldx, int oldy)方法有x,y,oldx,oldy四個參數,x,y分別代表着本次滑動時,控件距離原來位置(原點)的X軸、Y軸的距離;oldx,oldy分別代表上一次觸發該方法時,控件距離原來位置(原點)的x,y軸距離。由於我們只需要監聽Y軸方向,所以只需要傳遞Y軸方向的參數。所以我們採用接口回調的方式,定義一個接口,在onScrollChanged方法中將滑動的豎直距離y 以及手指滑動方向:isUp利用接口回調到Activity去。
實現步驟
Step1: 自定義ScrollView
package com.titlebargradient.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;
/**
* TODO<自定義監聽滑動的ScrollView>
*
* @author: 小嵩
* @date: 2017/1/9 11:37
*/
public class ObservableScrollView extends ScrollView {
private ScrollViewListener scrollViewListener = null;
public ObservableScrollView(Context context) {
super(context);
}
public ObservableScrollView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
}
public ObservableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setOnScrollListener(ScrollViewListener scrollViewListener) {
this.scrollViewListener = scrollViewListener;
}
@Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
super.onScrollChanged(x, y, oldx, oldy);
if (scrollViewListener != null) {
if (oldy < y ) {// 手指向上滑動,屏幕內容下滑
scrollViewListener.onScroll(oldy,y,false);
} else if (oldy > y ) {// 手指向下滑動,屏幕內容上滑
scrollViewListener.onScroll(oldy,y,true);
}
}
}
public interface ScrollViewListener{//dy Y軸滑動距離,isUp 是否返回頂部
void onScroll(int oldy,int dy,boolean isUp);
}
}
Step2. 佈局文件引用自定義的Scrollview
控件路徑替換成自己項目自定義ScrollView的路徑
<com.titlebargradient.widget.ObservableScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.titlebargradient.widget.ObservableScrollView>
Step3. xml頁面佈局編寫
完整XML佈局代碼如下:
activity_scrollview.xml
<?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"
android:orientation="vertical">
<LinearLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.titlebargradient.widget.ObservableScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="250dp">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:textSize="20sp"
android:lineSpacingExtra="10dp"
android:text="@string/TextContent"
android:gravity="center"/>
</LinearLayout>
</com.titlebargradient.widget.ObservableScrollView>
</LinearLayout>
<LinearLayout
android:id="@+id/lv_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_header"
android:layout_width="match_parent"
android:layout_height="@dimen/head_height"
android:scaleType="centerCrop"
android:layout_gravity="center"
android:src="@mipmap/bg_header"/>
</LinearLayout>
<include layout="@layout/layout_toolbar"/>
<LinearLayout
android:id="@+id/lv_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_alignParentBottom="true">
<include layout="@layout/layout_bottom"/>
</LinearLayout>
</RelativeLayout>
activity_scrollview.xml中引用的layout_toolbar.xml 標題欄:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/title_height"
android:background="@color/color_transparent">
<!--返回按鈕-->
<ImageView
android:id="@+id/iv_back"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="8dp"
android:src="@mipmap/ic_back"
android:background="@drawable/bg_circle"/>
<ImageView
android:id="@+id/iv_more"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="10dp"
android:src="@mipmap/ic_more"
android:layout_gravity="right"
android:layout_marginRight="8dp"
android:background="@drawable/bg_circle"/>
<ImageView
android:id="@+id/iv_shopping_cart"
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="8dp"
android:src="@mipmap/ic_shopping_cart"
android:layout_gravity="right"
android:layout_marginRight="8dp"
android:background="@drawable/bg_circle"/>
</android.support.v7.widget.Toolbar>
<View
android:id="@+id/spite_line"
android:layout_width="match_parent"
android:layout_height="0.8dp"
android:background="@color/color_light_gray"
android:visibility="gone"/>
</LinearLayout>
所引用的layout_bottom.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/bg_square"
android:gravity="center"
android:padding="8dp"
android:layout_gravity="center"
android:drawableTop="@mipmap/ic_help"
android:drawablePadding="2dp"
android:text="客服"
android:textSize="11sp" />
<TextView
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/bg_square"
android:gravity="center"
android:padding="8dp"
android:layout_gravity="center"
android:drawableTop="@mipmap/ic_market"
android:drawablePadding="2dp"
android:text="店鋪"
android:textSize="11sp"/>
<TextView
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/bg_square"
android:gravity="center"
android:padding="8dp"
android:layout_gravity="center"
android:drawableTop="@mipmap/ic_collection"
android:drawablePadding="2dp"
android:text="收藏"
android:textSize="11sp"/>
<TextView
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="48dp"
android:background="@color/color_orange"
android:gravity="center"
android:text="加入購物車"
android:textColor="@android:color/white"/>
<TextView
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="48dp"
android:background="@color/color_red"
android:gravity="center"
android:text="立即購買"
android:textColor="@android:color/white"/>
</LinearLayout>
其中,ICON 是從iconfont官網下載的,可自行搜索網站鏈接:阿里巴巴矢量圖庫
到這裏我們的佈局就已經寫好了,接下來就是在Activity中監聽滑動事件,然後實現漸變效果。
Step4. 監聽滑動距離
在Activity中,初始化自定義Scrollview並調用接口,實現對滑動距離的監聽**
部分關鍵代碼如下,完整代碼可下載demo進行研究:
//獲取dimen屬性中 標題和頭部圖片的高度
final float title_height = getResources().getDimension(R.dimen.title_height);
final float head_height = getResources().getDimension(R.dimen.head_height);
//滑動事件回調監聽(一次滑動的過程一般會連續觸發多次)
scrollView.setOnScrollListener(new ObservableScrollView.ScrollViewListener() {
@Override
public void onScroll(int oldy, int dy, boolean isUp) {
/* DensityUtil Density = new DensityUtil();
int mHeaderHeight_px = Density.dip2px(ScrollViewActivity.this, 200.0f);*/
float move_distance = head_height - title_height;
if (!isUp && dy <= move_distance) {//手指往上滑,距離未超過200dp
//標題欄逐漸從透明變成不透明
toolbar.setBackgroundColor(ContextCompat.getColor(ScrollViewActivity.this, R.color.color_white));
TitleAlphaChange(dy, move_distance);//標題欄漸變
HeaderTranslate(dy);//圖片視差平移
} else if (!isUp && dy > move_distance) {//手指往上滑,距離超過200dp
TitleAlphaChange(1, 1);//設置不透明百分比爲100%,防止因滑動速度過快,導致距離超過200dp,而標題欄透明度卻還沒變成完全不透的情況。
HeaderTranslate(head_height);//這裏也設置平移,是因爲不設置的話,如果滑動速度過快,會導致圖片沒有完全隱藏。
ivBack.setImageResource(R.mipmap.ic_back_dark);
ivMore.setImageResource(R.mipmap.ic_more_dark);
ivShoppingCart.setImageResource(R.mipmap.ic_shopping_dark);
spiteLine.setVisibility(View.VISIBLE);
} else if (isUp && dy > move_distance) {//返回頂部,但距離頭部位置大於200dp
//不做處理
} else if (isUp && dy <= move_distance) {//返回頂部,但距離頭部位置小於200dp
//標題欄逐漸從不透明變成透明
TitleAlphaChange(dy, move_distance);//標題欄漸變
HeaderTranslate(dy);//圖片視差平移
ivBack.setImageResource(R.mipmap.ic_back);
ivMore.setImageResource(R.mipmap.ic_more);
ivShoppingCart.setImageResource(R.mipmap.ic_shopping_cart);
spiteLine.setVisibility(View.GONE);
}
}
});
Step5. 設置標題欄透明度變化:
private void TitleAlphaChange(int dy, float mHeaderHeight_px) {//設置標題欄透明度變化
float percent = (float) Math.abs(dy) / Math.abs(mHeaderHeight_px);
//如果是設置背景透明度,則傳入的參數是int類型,取值範圍0-255
//如果是設置控件透明度,傳入的參數是float類型,取值範圍0.0-1.0
//這裏我們是設置背景透明度就好,因爲設置控件透明度的話,返回ICON等也會變成透明的。
//alpha 值越小越透明
int alpha = (int) (percent * 255);
toolbar.getBackground().setAlpha(alpha);//設置控件背景的透明度,傳入int類型的參數(範圍0~255)
ivBack.getBackground().setAlpha(255 - alpha);
ivMore.getBackground().setAlpha(255 - alpha);
ivShoppingCart.getBackground().setAlpha(255 - alpha);
}
圖片滑動視差效果:
private void HeaderTranslate(float distance) {
lvHeader.setTranslationY(-distance);
ivHeader.setTranslationY(distance/2);
}
結尾傳送門
GitHub項目地址:TitlebarGradient-淘寶購物詳情頁標題欄漸變。
仿知乎、美團效果的可參考我另外一篇博客:http://blog.csdn.net/qq_22393017/article/details/54377603
歡迎交流討論、有問題也非常歡迎指出來,覺得不錯的話請Star一下哦,大家的支持將會讓人更有動力繼續分享~