天天看點

安卓項目實戰之實作頂部标題欄沉浸式漸變效果

現在越來越多的app頁面都有随着主體布局的滾動頂部标題欄漸變效果,有的是自定義的layout有的則是Toolbar,有的頁面主體布局是ScrollView,有的則是RecycleView,今天我們就來一起實作這樣的效果。

效果圖如下:

安卓項目實戰之實作頂部标題欄沉浸式漸變效果
實作步驟:

ScrollView 6.0以前沒有scrollView.setOnScrollChangeListener(l)方法 是以要自定義scrollview的code,因為scrollview的滑動監聽不相容低版本.

1、繼承ScrollView實作自定義的ObservableScrollView.java類,代碼如下:

public class ObservableScrollView extends ScrollView {

    /**
     * 回調接口監聽事件
     */
    private OnObservableScrollViewListener mOnObservableScrollViewListener;

    public ObservableScrollView(Context context) {
        super(context);
    }

    public ObservableScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ObservableScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    /**
     * 添加回調接口,便于把滑動事件的資料向外抛
     */
    public interface OnObservableScrollViewListener {
        void onObservableScrollViewListener(int l, int t, int oldl, int oldt);
    }

    /**
     * 注冊回調接口監聽事件
     *
     * @param onObservableScrollViewListener
     */
    public void setOnObservableScrollViewListener(OnObservableScrollViewListener onObservableScrollViewListener) {
        this.mOnObservableScrollViewListener = onObservableScrollViewListener;
    }

    /**
     * 滑動監聽
     * This is called in response to an internal scroll in this view (i.e., the
     * view scrolled its own contents). This is typically as a result of
     * {@link #scrollBy(int, int)} or {@link #scrollTo(int, int)} having been
     * called.
     *
     * @param l Current horizontal scroll origin. 目前滑動的x軸距離
     * @param t Current vertical scroll origin. 目前滑動的y軸距離
     * @param oldl Previous horizontal scroll origin. 上一次滑動的x軸距離
     * @param oldt Previous vertical scroll origin. 上一次滑動的y軸距離
     */
    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (mOnObservableScrollViewListener != null) {
            //将監聽到的資料向外抛
            mOnObservableScrollViewListener.onObservableScrollViewListener(l, t, oldl, oldt);
        }
    }
}
           

2、頁面布局代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.administrator.myapplication.widgets.ObservableScrollView
        android:id="@+id/sv_main_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:scrollbars="none">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <TextView
                android:id="@+id/tv_main_topContent"
                android:layout_width="match_parent"
                android:layout_height="280dp"
                android:background="#b5b433"
                android:gravity="center"
                android:src="@mipmap/ic_launcher"
                android:text="我是頭部"
                android:textSize="22sp" />

            <TextView
                android:id="@+id/tvShow"
                android:layout_width="match_parent"
                android:layout_height="600dp"
                android:background="#ffffff"
                android:gravity="center"
                android:src="@mipmap/ic_launcher"
                android:text="我是内容"
                android:textSize="22sp" />

        </LinearLayout>
    </com.example.administrator.myapplication.widgets.ObservableScrollView>
    
    <include layout="@layout/include_header_itl" />

</RelativeLayout>
           

3、include_header_itl.xml為頂部标題欄布局檔案,代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_header_content"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#00000000"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:orientation="horizontal"
        android:paddingTop="25dp">

        <ImageView
            android:id="@+id/iv_header_left"
            android:layout_width="40dp"
            android:layout_height="match_parent"
            android:paddingLeft="8dp"
            android:paddingRight="12dp"
            android:src="@mipmap/ic_launcher" />


        <TextView
            android:id="@+id/tv_header_title"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_centerHorizontal="true"
            android:ellipsize="end"
            android:gravity="center"
            android:maxLines="2"
            android:text="我是标題"
            android:textColor="#ffffff"
            android:textSize="16sp"
            android:textStyle="bold" />


        <ImageView
            android:id="@+id/iv_header_img"
            android:layout_width="40dp"
            android:layout_height="match_parent"
            android:layout_alignParentRight="true"
            android:layout_gravity="center"
            android:paddingLeft="12dp"
            android:paddingRight="8dp"
            android:src="@mipmap/ic_launcher" />

    </RelativeLayout>

</LinearLayout>
           

4、首頁面邏輯代碼:

public class FragmentTab02 extends BaseFragment implements ObservableScrollView.OnObservableScrollViewListener {

    @BindView(R.id.sv_main_content)
    ObservableScrollView mObservableScrollView;
    @BindView(R.id.tv_main_topContent)
    TextView tvImage; // 标題欄覆寫下的頭部
    @BindView(R.id.ll_header_content)
    LinearLayout mHeaderContent; // 标題欄
    @BindView(R.id.tvShow)
    TextView tvShow; // 标題欄覆寫下的内容

    private int mHeight;

    @Override
    protected int getContentViewId() {
        return R.layout.fragment_tab02;
    }

    @Override
    protected void initData() {
        // 設定沉浸式效果,或者在fragment所依附的activity中設定也可以
        setStatusBarTranslucent(getActivity());
        //注冊滑動監聽
        mObservableScrollView.setOnObservableScrollViewListener(Fragment2Tab02.this);
    }

    /**
     * 我們可以利用OnGlobalLayoutListener來獲得一個視圖的真實高度
     * 但是需要注意的是OnGlobalLayoutListener可能會被多次觸發,是以在得到了高度之後,要将OnGlobalLayoutListener登出掉
     * 注意在fragment中使用:
     * 如果有多個fragment通過show和hide顯示和隐藏,并且進入activity後顯示的不是這個fragment,那麼在onViewCreate()裡調用此方法測得的寬高依然為0!
     * 解決方案:
     * 重寫onHiddenChanged()方法,并在裡面判斷當fragment不隐藏的時候調用此方法即可正常擷取,如下:
     */
    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        if (!hidden) {
            final ViewTreeObserver viewTreeObserver = tvImage.getViewTreeObserver();
            viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    tvImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    mHeight = tvImage.getHeight() - mHeaderContent.getHeight();//這裡取的高度應該為圖檔的高度-标題欄
                }
            });
        }
    }

    /**
     * 擷取ObservableScrollView的滑動資料
     *
     * @param l
     * @param t
     * @param oldl
     * @param oldt
     */
    @Override
    public void onObservableScrollViewListener(int l, int t, int oldl, int oldt) {
        if (t <= 0) {
            //頂部圖處于最頂部,标題欄透明
            mHeaderContent.setBackgroundColor(Color.argb(0, 48, 63, 159));
        } else if (t > 0 && t < mHeight) {
            //滑動過程中,漸變
            float scale = (float) t / mHeight;//算出滑動距離比例
            float alpha = (255 * scale);//得到透明度
            mHeaderContent.setBackgroundColor(Color.argb((int) alpha, 48, 63, 159));
        } else {
            //過頂部圖區域,标題欄定色
            mHeaderContent.setBackgroundColor(Color.argb(255, 48, 63, 159));
        }
    }

    @OnClick(R.id.tvShow)
    public void onClick(){
        startActivity(new Intent(mActivity, SearchActivity.class));
    }
   
    /**
     * 設定activity全屏,狀态欄透明,内容填充到狀态欄中
     */
    public static void setStatusBarTranslucent(Activity activity) {
        // 5.0 以上
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            View decorView = activity.getWindow().getDecorView();
            decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
            activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
        }
    }
}
           

重要知識點:

final ViewTreeObserver viewTreeObserver = tvImage.getViewTreeObserver();
            viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    tvImage.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    mHeight = tvImage.getHeight() - mHeaderContent.getHeight();//這裡取的高度應該為圖檔的高度-标題欄
                }
            });
           

使用上述代碼擷取控件寬高時,如果是在Activity中,那麼可直接在onCreate方法中使用,但如果有多個fragment通過show和hide顯示和隐藏,并且進入activity後顯示的不是這個fragment,那麼在onViewCreate()裡調用此方法測得的寬高依然為0!,此例子就是,該效果是在第二個Fragment中實作的,并且該fragment也是通過show和hide控制顯示和隐藏。

解決方案:

重寫onHiddenChanged()方法,并在裡面判斷當fragment不隐藏的時候調用此方法即可正常擷取,如上面的寫法。

RecyclerView滑動時使toolsbar漸變

1,布局檔案如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="txkj.xian.com.myapplication.activity.LayoutActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rvShow"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </android.support.v7.widget.RecyclerView>

    <RelativeLayout
        android:id="@+id/toobar"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="#00000000"
        >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:textColor="#ffffff"
            android:textSize="16dp"
            android:text="我是标題"/>

    </RelativeLayout>

</RelativeLayout>

           

2,Activity中:

public class LayoutActivity extends AppCompatActivity {

    private RecyclerView rvShow;
    private RelativeLayout toobar;

    private int mDistanceY;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_layout);

        rvShow = findViewById(R.id.rvShow);
        toobar = findViewById(R.id.toobar);

        LinearLayoutManager manager = new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false);
        rvShow.setLayoutManager(manager);

        List<String> list = new ArrayList<>();
        for(int i=0;i<30;i++){
            list.add("條目"+i);
        }
        MyAdapter adapter = new MyAdapter(R.layout.item,list);
        rvShow.setAdapter(adapter);

        setListener();
    }

    private void setListener() {
        rvShow.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                //滑動的距離
                mDistanceY += dy;
                //toolbar的高度
                int toolbarHeight = toobar.getBottom();  // getBottom()名額題欄底部距離父視窗的距離也就是标題欄的高度

                //當滑動的距離 <= toolbar高度的時候,改變Toolbar背景色的透明度,達到漸變的效果
                if (mDistanceY <= toolbarHeight) {
                    float scale = (float) mDistanceY / toolbarHeight;
                    float alpha = scale * 255;
                    toobar.setBackgroundColor(Color.argb((int) alpha, 63, 81, 181));
                } else {
                    //上述雖然判斷了滑動距離與toolbar高度相等的情況,但是實際測試時發現,标題欄的背景色
                    //很少能達到完全不透明的情況,是以這裡又判斷了滑動距離大于toolbar高度的情況,
                    //将标題欄的顔色設定為完全不透明狀态
                    toobar.setBackgroundResource(R.color.colorPrimary); // #3F51B5
                }

            }
        });

    }


    public class MyAdapter extends BaseQuickAdapter<String, BaseViewHolder> {

        public MyAdapter(@LayoutRes int layoutResId, @Nullable List<String> data) {
            super(layoutResId, data);
        }

        @Override
        protected void convert(BaseViewHolder helper, String item) {
            // 指派
            helper.setText(R.id.content,item);
        }
    }

}
});