之前幫朋友寫(xiě)了這樣的一個(gè)效果:標(biāo)題欄中的元素隨著屏幕的滑動(dòng)發(fā)生漸變的效果枪眉,有文字大小的漸變也有透明度和距離的漸變等,后面沒(méi)有及時(shí)更新上來(lái)献联,今天補(bǔ)一下巴粪。效果圖如下:
思路 + 原理
OK,先來(lái)說(shuō)下思路受扳,很容易就能想到圖中漸變的效果是根據(jù)滑動(dòng)監(jiān)聽(tīng)一點(diǎn)點(diǎn)動(dòng)態(tài)變化的携龟,這里我們就可以監(jiān)聽(tīng)NestedScrollView的滾動(dòng)(or其他兔跌,我這里主頁(yè)用的是NestedScrollView)勘高。
原理:在FrameLayout中放一個(gè)LinearLayout,置于底部坟桅,然后設(shè)置LinearLayout距離頂部一段距離华望,假設(shè)marginTop是50dp,只要在監(jiān)聽(tīng)滾動(dòng)的時(shí)候動(dòng)態(tài)改變這個(gè)marginTop的值就好了仅乓,這是整體LinearLayout上移的效果實(shí)現(xiàn)赖舟,至于文字的大小和透明度的改變,也是同樣的原理夸楣,根據(jù)滾動(dòng)距離做出同步修改即可宾抓。為了容易理解,我畫(huà)了一張示意草圖豫喧,看了即可一目了然石洗。
OK,接下來(lái)放出xml的布局代碼:
布局:
<?xml version="1.0" encoding="utf-8"?>
<com.zhuyong.dogdemo.CustomFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/coordinator_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.NestedScrollView
android:id="@+id/sc"
android:layout_width="match_parent"
android:layout_height="match_parent">
********//省略
</android.support.v4.widget.NestedScrollView>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:gravity="bottom"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/child_root_marginTop"
android:gravity="center_vertical"
android:minHeight="50dp"
android:paddingLeft="12dp"
android:paddingTop="5dp"
android:paddingRight="12dp"
android:paddingBottom="5dp">
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="標(biāo)題"
android:textColor="#FFFFFF"
android:textSize="@dimen/text_size_30sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:onClick="scanCode"
android:orientation="vertical">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/icon_scan" />
<TextView
android:id="@+id/tv_scan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/text_right_marginTop"
android:text="掃一掃"
android:textColor="#FFFFFF"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:gravity="center"
android:onClick="invitationCode"
android:orientation="vertical">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@drawable/icon_invinte" />
<TextView
android:id="@+id/tv_invitation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/text_right_marginTop"
android:text="邀請(qǐng)碼"
android:textColor="#FFFFFF"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
</com.zhuyong.dogdemo.CustomFrameLayout>
接下來(lái)就是自定義最外層的FrameLayout了紧显,前戲不多說(shuō)了讲衫,直接上主要代碼:
1、獲取子控件孵班,初始化相關(guān)變量
/**
* 獲取第一個(gè)childView轉(zhuǎn)化為NestedScrollView
*/
NestedScrollView mSestedScrollView = (NestedScrollView) getChildAt(0);
/**
* 獲取第二個(gè)childView,也就是我們的Toolbar部分
*/
final ViewGroup mViewToolbarRoot = (ViewGroup) getChildAt(1);
final ViewGroup mViewChildRoot = (ViewGroup) mViewToolbarRoot.getChildAt(0);
final TextView mTvTitle = mViewChildRoot.findViewById(R.id.tv_title);
final TextView mTvScan = mViewChildRoot.findViewById(R.id.tv_scan);
final TextView mTvInvitation = mViewChildRoot.findViewById(R.id.tv_invitation);
/**
* 獲取整個(gè)狀態(tài)欄的高度涉兽,用來(lái)控制一些其他變量
*/
final int mHeaderHeight = mViewToolbarRoot.getMeasuredHeight();
/**
* 設(shè)置NestedScrollView的paddingTop值防止被mViewToolbarRoot遮蓋
*/
mSestedScrollView.setPadding(getPaddingLeft(), getPaddingTop() + mHeaderHeight, getPaddingRight(), getPaddingBottom());
final FrameLayout.LayoutParams paramsChildRoot = (FrameLayout.LayoutParams) mViewChildRoot.getLayoutParams();
final LinearLayout.LayoutParams paramsChildTv = (LinearLayout.LayoutParams) mTvInvitation.getLayoutParams();
2、根據(jù)scrollY動(dòng)態(tài)更新LayoutParams篙程、透明度枷畏、文字大小等值
還記得我們那個(gè)50dp的topMaginHeight吧,這里就根據(jù)它的大小和scrollY作比較判斷滾動(dòng)距離虱饿,來(lái)確定是否需要漸變拥诡。
如果滑動(dòng)距離大于topMaginHeight,狀態(tài)固定郭厌,不做改變袋倔。
mTvScan.setAlpha(0);
mTvInvitation.setAlpha(0);
mTvTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize / 2);
paramsChildRoot.topMargin = 0;
mViewChildRoot.setLayoutParams(paramsChildRoot);
/**
* 控制右邊文字的移動(dòng)
*/
paramsChildTv.topMargin = -mHeaderHeight;
mTvScan.setLayoutParams(paramsChildTv);
mTvInvitation.setLayoutParams(paramsChildTv);
當(dāng)scrollY <= topMaginHeight時(shí):
float scale = (float) scrollY / topMaginHeight;
/**
* 控制右邊文字的透明度
*/
mTvScan.setAlpha(1 - offset * scale);
mTvInvitation.setAlpha(1 - offset * scale);
/**
* 控制標(biāo)題文字的大小
*/
scale = (1 - scale) > 0.5f ? (1 - scale) : 0.5f;
mTvTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize * scale);
/**
* 控制整個(gè)頭布局的高度
*/
paramsChildRoot.topMargin = topMaginHeight - scrollY;
mViewChildRoot.setLayoutParams(paramsChildRoot);
/**
* 控制右邊文字的移動(dòng)
*/
paramsChildTv.topMargin = (int) (-scrollY * offset + textMaginTop);
mTvScan.setLayoutParams(paramsChildTv);
mTvInvitation.setLayoutParams(paramsChildTv);
源碼
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.widget.NestedScrollView;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* 自定義FrameLayout實(shí)現(xiàn)自定義伸縮及懸浮效果
* 作者:zhuyong on 2019/3/31 20:21
* 郵箱:99305919@qq.com
* 希望每天叫醒你的不是鬧鐘而是夢(mèng)想
*/
public class CustomFrameLayout extends FrameLayout {
private Context mContext;
public CustomFrameLayout(@NonNull Context context) {
this(context, null);
}
public CustomFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
}
private void initView() {
/**
* 獲取第一個(gè)childView轉(zhuǎn)化為NestedScrollView
*/
NestedScrollView mSestedScrollView = (NestedScrollView) getChildAt(0);
/**
* 獲取第二個(gè)childView,也就是我們的Toolbar部分
*/
final ViewGroup mViewToolbarRoot = (ViewGroup) getChildAt(1);
final ViewGroup mViewChildRoot = (ViewGroup) mViewToolbarRoot.getChildAt(0);
final TextView mTvTitle = mViewChildRoot.findViewById(R.id.tv_title);
final TextView mTvScan = mViewChildRoot.findViewById(R.id.tv_scan);
final TextView mTvInvitation = mViewChildRoot.findViewById(R.id.tv_invitation);
/**
* 獲取整個(gè)狀態(tài)欄的高度,用來(lái)控制一些其他變量
*/
final int mHeaderHeight = mViewToolbarRoot.getMeasuredHeight();
/**
* 設(shè)置NestedScrollView的paddingTop值防止被mViewToolbarRoot遮蓋
*/
mSestedScrollView.setPadding(getPaddingLeft(), getPaddingTop() + mHeaderHeight, getPaddingRight(), getPaddingBottom());
final FrameLayout.LayoutParams paramsChildRoot = (FrameLayout.LayoutParams) mViewChildRoot.getLayoutParams();
final LinearLayout.LayoutParams paramsChildTv = (LinearLayout.LayoutParams) mTvInvitation.getLayoutParams();
final int textMaginTop = (int) mContext.getResources().getDimension(R.dimen.text_right_marginTop);//文字距離圖標(biāo)的距離
final int topMaginHeight = (int) mContext.getResources().getDimension(R.dimen.child_root_marginTop);//標(biāo)題距離頂部距離
final float textSize = (int) mContext.getResources().getDimension(R.dimen.text_size_30sp);//標(biāo)題文字大小
if (topMaginHeight <= 0) {
return;
}
//梯度值折柠,用來(lái)調(diào)整相關(guān)的速度
final float offset = 1.7f;
mSestedScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
@Override
public void onScrollChange(NestedScrollView nestedScrollView, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if (scrollY <= topMaginHeight) {
float scale = (float) scrollY / topMaginHeight;
/**
* 控制右邊文字的透明度
*/
mTvScan.setAlpha(1 - offset * scale);
mTvInvitation.setAlpha(1 - offset * scale);
/**
* 控制標(biāo)題文字的大小
*/
scale = (1 - scale) > 0.5f ? (1 - scale) : 0.5f;
mTvTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize * scale);
/**
* 控制整個(gè)頭布局的高度
*/
paramsChildRoot.topMargin = topMaginHeight - scrollY;
mViewChildRoot.setLayoutParams(paramsChildRoot);
/**
* 控制右邊文字的移動(dòng)
*/
paramsChildTv.topMargin = (int) (-scrollY * offset + textMaginTop);
mTvScan.setLayoutParams(paramsChildTv);
mTvInvitation.setLayoutParams(paramsChildTv);
} else {
/**
* 當(dāng)滑動(dòng)距離大于topMaginHeight宾娜,狀態(tài)固定
*/
mTvScan.setAlpha(0);
mTvInvitation.setAlpha(0);
mTvTitle.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize / 2);
paramsChildRoot.topMargin = 0;
mViewChildRoot.setLayoutParams(paramsChildRoot);
/**
* 控制右邊文字的移動(dòng)
*/
paramsChildTv.topMargin = -mHeaderHeight;
mTvScan.setLayoutParams(paramsChildTv);
mTvInvitation.setLayoutParams(paramsChildTv);
}
}
});
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
/**
* 在ViewGroup位置擺放完成之后,開(kāi)始處理
*/
initView();
}
}