仿全歷史——全沉浸時間軸實現(xiàn)

前言

我又來了!前段時間由于夏天的燥熱與自身的懶惰奴迅,一直沒有更新博客哈哈哈青责。今天良心發(fā)現(xiàn),來更一個比較有意思的東西取具,如題——仿全歷史APP的全沉浸時間軸實現(xiàn)脖隶。
話不多說,先上圖


Screenshot_20200530_225923_com.allhistory.dls.mar.jpg

這張圖呢暇检,就是全歷史App中全古古跡功能的界面圖产阱。
顯然当船,它通過沉浸狀態(tài)欄义屏、透明背景、recyclerView的自定義Item等则酝,實現(xiàn)了一個很優(yōu)秀的界面效果悔据。
今天庄敛,我要做的就是猜測這效果背后的實現(xiàn)原理,并仿制一個類似的界面

正文

解構(gòu)

我們先看看結(jié)構(gòu)一下這效果背后的組成部分:

Screenshot_20200530_225923_com.allhistory.dls.mar_副本.jpg

總的來講科汗,實現(xiàn)這樣的效果主要需要四個部分:
1.沉浸狀態(tài)欄藻烤、透明toolbar與背景
2.TabLayout樣式自定義
3.TextSwitcher文字切換
(個人猜測是有可能是使用TextSwitcher實現(xiàn)的,實際今天的仿制里不涉及這個哈,今天的重點是沉浸與時間軸怖亭。如果這個也包含涎显,篇幅就控制不住了......)
4.ItemDecoration定制,繪圖畫出時間軸效果

實現(xiàn)

因為涉及到的內(nèi)容比較多兴猩,所以只會放出部分關(guān)鍵的代碼哈期吓。

1.沉浸狀態(tài)欄與透明背景

狀態(tài)欄的沉浸涉及到不同版本、不同情景狀態(tài)下的適配峭跳,相對來講比較復(fù)雜膘婶。想要研究的比較深的朋友,可以查看http://www.reibang.com/p/dc20e98b9a90這篇博客蛀醉,內(nèi)容充實悬襟,講的也非常詳細,本文的的沉浸適配也對其進行了參考。
我們今天涉及到的沉浸狀態(tài)欄實際非常簡單拯刁,準確的講應(yīng)該叫透明狀態(tài)欄脊岳,也并不包含交互效果下狀態(tài)欄的變換。
本文采用了透明主題的配置垛玻,如下所示:

API21 之后(也就是 android 5.0 之后)的狀態(tài)欄割捅,會默認覆蓋一層半透明遮罩。且為了保持4.4以前系統(tǒng)正常使用帚桩,故需要三份 style 文件亿驾,即默認的values(不設(shè)置狀態(tài)欄透明)、values-v19账嚎、values-v21(解決半透明遮罩問題)>

//valuse
<style name="TranslucentTheme" parent="AppTheme">
</style>

// values-v19莫瞬。v19 開始有 android:windowTranslucentStatus 這個屬性
<style name="TranslucentTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowTranslucentStatus">true</item>
        <item name="android:windowTranslucentNavigation">true</item>
</style>

// values-v21。5.0 以上提供了 setStatusBarColor()  方法設(shè)置狀態(tài)欄顏色郭蕉。
<style name="TranslucentTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowTranslucentStatus">false</item>
    <item name="android:windowTranslucentNavigation">true</item>
    <!--Android 5.x開始需要把顏色設(shè)置透明疼邀,否則導(dǎo)航欄會呈現(xiàn)系統(tǒng)默認的淺灰色-->
    <item name="android:statusBarColor">@android:color/transparent</item>
</style>

設(shè)置了透明主題之后,實際會布局會頂?shù)綘顟B(tài)欄召锈,因而需要為toolbar設(shè)置一個paddingTop

  /**
     * 設(shè)置頂部statusBar與頂部View同背景
     *
     * @param activity 需要設(shè)置的activity
     * @param topView  頂部需要設(shè)置padding的view
     */
    public static void  setStatusBarMergeWithTopView(Activity activity, final View topView) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            final int statusBarHeight = getStatusBarHeight(activity);
            topView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    topView.setPadding(0, statusBarHeight, 0, 0);
                    topView.getLayoutParams().height = topView.getHeight() + statusBarHeight;
                    topView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                }
            });
        }
    }

對應(yīng)Acitivity設(shè)置:
StatusBarUtil.setStatusBarMergeWithTopView(this, tbHistory);//tbHistory為需要設(shè)置的透明toolbar旁振,會在下面的布局中看到。
Acitivty布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="@drawable/historytest"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="match_parent">
            <androidx.appcompat.widget.Toolbar
                android:id="@+id/tb_history"
                android:layout_width="match_parent"
                android:layout_height="?actionBarSize"
                app:layout_collapseMode="pin"
                android:background="@android:color/transparent"
                android:minHeight="?actionBarSize"
                app:navigationIcon="@drawable/ic_personal_center_back"
                app:title="">
                <TextView
                    android:id="@+id/tv_title"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:text="全古跡"
                    android:textColor="@android:color/white"
                    android:textSize="@dimen/text_size_xlarge_18"
                    android:visibility="visible"
                    android:textStyle="bold" />
            </androidx.appcompat.widget.Toolbar>
            <com.google.android.material.tabs.TabLayout
                android:id="@+id/tl_history"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:tabMode="scrollable"
                app:tabGravity="fill"
                app:tabTextAppearance="@style/HistoryTestTabLayoutTextStyle"
                app:tabIndicatorHeight="0dp"
                app:tabPadding="@dimen/height_2"
                app:tabSelectedTextColor="@color/white"
                app:tabTextColor="@color/normal_grey"/>

            <androidx.viewpager.widget.ViewPager
                android:id="@+id/vp_fg"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/margin_2"
                android:layout_marginBottom="@dimen/margin_min_2" />
</LinearLayout>
 

這個布局文件除了Tablayout外并沒有什么好講的涨岁,所以我們直接進入第二部分

2.Tablayout自定義

全古跡中的tablayout主要就是進行了tab切換后字體的大小拐袜、粗細的變化,tab采用了滾動的模式梢薪,下劃線Indicator是自定義的一個較短的下劃線阻肿。
tablayout的下劃線默認是與Tab等長的,后來官方提供了app:tabIndicatorFullWidth="true"這個屬性來實現(xiàn)下劃線與字體等長沮尿。
但是顯然丛塌,達不到圖中的那種效果较解。
想要實現(xiàn)圖中的效果,主要有兩種方法赴邻,一種是通過反射拿到該控件來設(shè)置長度印衔,一種是自定義一個tabView。
這里我們選擇了第二種方式姥敛。
1.注意在tablayout布局文件中設(shè)置app:tabIndicatorHeight="0dp"奸焙。
2.編寫自定義TabView布局文件。

<?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="wrap_content"
    android:layout_gravity="center"
    android:orientation="horizontal">

    <TextView
        android:textSize="14sp"
        android:id="@+id/tv_tab_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:textColor="@color/white" />


    <View
        android:id="@+id/v_indicator"
        android:layout_width="14dp"
        android:layout_height="2dp"
        android:layout_below="@+id/tv_tab_title"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="5dp"
        android:visibility="invisible"
        android:background="#ffd700"/>


</RelativeLayout>
 

在對應(yīng)的FragmentPagerAdapter中加上

   public View getTabView(int position) {
            View tabView = LayoutInflater.from(getApplicationContext()).inflate(R.layout.item_tab_layout_history, null);
            TextView tabTitle = (TextView) tabView.findViewById(R.id.tv_tab_title);
            tabTitle.setText(getPageTitle(position));//需要對應(yīng)設(shè)置的tab標題
            return tabView;
        }

接下來就是設(shè)置viewPagerAdapter彤敛,以及監(jiān)聽tab選中事件与帆。

ViewPagerAdapter viewPagerAdapter =new ViewPagerAdapter(getSupportFragmentManager());
        vpFg.setAdapter(viewPagerAdapter);
        vpFg.setOffscreenPageLimit(0);
        tlHistory.setupWithViewPager(vpFg);
        vpFg.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tlHistory));
        for (int i = 0; i < tlHistory.getTabCount(); i++) {//設(shè)置自定義tabView
            View tabView = viewPagerAdapter.getTabView(i);
            if(i==0){//默認先加載第一個,所以第一個的字體等樣式先設(shè)置一下墨榄。
                View indicator = tabView.findViewById(R.id.v_indicator);
                indicator.setVisibility(View.VISIBLE);
                TextView tabTitle = (TextView) tabView.findViewById(R.id.tv_tab_title);
                tabTitle.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
                tabTitle.setTextSize(18);
                tabTitle.setTextColor(getResources().getColor(R.color.beige_yellow));
            }
            tlHistory.getTabAt(i).setCustomView(tabView);
        }
        tlHistory.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                //選中玄糟,則更改樣式
                View tabView = tab.getCustomView();
                if(tabView!=null){
                    TextView tabTitle = (TextView) tabView.findViewById(R.id.tv_tab_title);
                    tabTitle.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
                    tabTitle.setTextSize(18);
                    View indicator = tabView.findViewById(R.id.v_indicator);
                    indicator.setVisibility(View.VISIBLE);
                }
            }
            @Override
            public void onTabUnselected(TabLayout.Tab tab) {
                //未選中,則恢復(fù)樣式
                View tabView = tab.getCustomView();
                if(tabView!=null){
                    TextView tabTitle = (TextView) tabView.findViewById(R.id.tv_tab_title);
                    tabTitle.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
                    tabTitle.setTextSize(14);
                    View indicator = tabView.findViewById(R.id.v_indicator);
                    indicator.setVisibility(View.INVISIBLE);
                }
            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {

            }
        });
        vpFg.setCurrentItem(0);
        vpFg.setOffscreenPageLimit(3);
        tlHistory.getTabAt(0).select();

直到這里袄秩,已經(jīng)完成了大半部分嘍阵翎,還剩最后的時間軸的繪制。

3.時間軸繪制

時間軸的繪制呢之剧,我們需要借用設(shè)置itemDecoration郭卫。
什么是itemDecoration?我們來看下官方的定義:

An ItemDecoration allows the application to add a special drawing and layout offset to specific item views from the adapter's data set. This can be useful for drawing dividers between items, highlights, visual grouping boundaries and more.>

ItemDecoration允許應(yīng)用結(jié)合adapter的數(shù)據(jù)集,對特定的item添加繪制一個周邊圖案背稼》【可以用于給items之間添加分割線、高亮裝飾效果或者分組邊界等等蟹肘。>

顧名思義词疼,ItemDecoration就是對item起裝飾作用,其最常見的用法就是添加分割線疆前。
在這里的時間軸寒跳,需要畫出圓環(huán)聘萨、圓點竹椒、線段、文字米辐。
完整代碼如下:

package com.example.ctccp.personalcenter.ui.adapter;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;

import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;


public class TimeAxisItemDecoration extends RecyclerView.ItemDecoration {
    private Paint LinePaint;
    private Paint TextPaint;
    private Paint PointPaint;
    private List<String> dynasty;
    private int divide_width = 66;

    public TimeAxisItemDecoration(Context context, List<String> dynasty) {
        LinePaint = new Paint();
        TextPaint = new Paint();
        PointPaint =new Paint();
        PointPaint.setStyle(Paint.Style.FILL);
        PointPaint.setStrokeWidth(8);
        PointPaint.setStrokeCap(Paint.Cap.ROUND);
        PointPaint.setColor(Color.WHITE);
        LinePaint.setColor(Color.WHITE);
        TextPaint.setColor(Color.WHITE);
        this.dynasty = dynasty;
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        int childCount = parent.getChildCount();
        RecyclerView.LayoutManager manager = parent.getLayoutManager();//為了動態(tài)獲取
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            int cx = manager.getLeftDecorationWidth(child) / 2;
            int cy = child.getTop()-8;
            float circleRadius = 14;
            int TextSize = 32;

            LinePaint.setStrokeWidth((float) 2.0);
            LinePaint.setStyle(Paint.Style.STROKE);

            c.drawCircle(cx,cy,circleRadius,LinePaint);//圓環(huán)
            c.drawPoint(cx,cy, PointPaint);//圓點
            c.drawLine(cx, child.getTop(), cx, child.getBottom()+child.getHeight(), LinePaint);//線

            TextPaint.setTextSize(TextSize);
        }
    }

    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        RecyclerView.LayoutManager manager = parent.getLayoutManager();//為了動態(tài)獲取
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            int cx = manager.getLeftDecorationWidth(child) ;
            int index = parent.getChildAdapterPosition(child);
            if(index<dynasty.size())
                c.drawText(dynasty.get(index), cx+8 , child.getTop() , TextPaint);
        }
    }


    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        if(parent.getLayoutManager()instanceof LinearLayoutManager){
            outRect.set(divide_width,divide_width,divide_width/2,0);
        }

        //outRect outRect就是表示在item的上下左右所撐開的距離
        //View view:是指當(dāng)前Item的View對象
        //RecyclerView parent: 是指RecyclerView 本身
        //RecyclerView.State state:通過State可以獲取當(dāng)前RecyclerView的狀態(tài)胸完,也可以通過State在RecyclerView各組件間傳遞參數(shù)
    }
}

成果

因為重點是沉浸和時間軸所以沒有實現(xiàn)TextSwitcher的部分,數(shù)據(jù)也是統(tǒng)一填充的(害 翘贮,懶就一個字)赊窥,實際toolbar使用的是默認尺寸,toolbar高度再低點狸页,應(yīng)該視覺效果會更好一點锨能。


Screenshot_20200531_175330_com.example.ctccp.jpg

后記

全歷史實屬一個良心扯再、炫酷的app(害,全歷史應(yīng)該給我打5毛到卡里址遇,我這還免費宣傳了一波)熄阻。
溜了溜了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末倔约,一起剝皮案震驚了整個濱河市秃殉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌浸剩,老刑警劉巖钾军,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異绢要,居然都是意外死亡吏恭,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門袖扛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來砸泛,“玉大人,你說我怎么就攤上這事蛆封〈浇福” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵惨篱,是天一觀的道長盏筐。 經(jīng)常有香客問我,道長砸讳,這世上最難降的妖魔是什么琢融? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮簿寂,結(jié)果婚禮上漾抬,老公的妹妹穿的比我還像新娘。我一直安慰自己常遂,他們只是感情好纳令,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著克胳,像睡著了一般平绩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上漠另,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天捏雌,我揣著相機與錄音,去河邊找鬼笆搓。 笑死性湿,一個胖子當(dāng)著我的面吹牛纬傲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肤频,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼嘹锁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了着裹?” 一聲冷哼從身側(cè)響起领猾,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎骇扇,沒想到半個月后摔竿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡少孝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年继低,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稍走。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡袁翁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出婿脸,到底是詐尸還是另有隱情粱胜,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布狐树,位于F島的核電站焙压,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏抑钟。R本人自食惡果不足惜涯曲,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望在塔。 院中可真熱鬧幻件,春花似錦、人聲如沸蛔溃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽城榛。三九已至揪利,卻和暖如春态兴,著一層夾襖步出監(jiān)牢的瞬間狠持,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工瞻润, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留喘垂,地道東北人甜刻。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像正勒,于是被迫代替她去往敵國和親得院。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359