android app自动更新界面_Android仿全历史——全沉浸时间轴实现

本文介绍了如何在Android应用中实现全沉浸式的时间轴效果,包括沉浸状态栏与透明背景的设置,TabLayout的自定义以及时间轴的绘制。通过这些步骤,可以创建出类似全历史应用的时间轴展示效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

我又来了!前段时间由于夏天的燥热与自身的懒惰,一直没有更新博客哈哈哈。今天良心发现,来更一个比较有意思的东西,如题——仿全历史APP的全沉浸时间轴实现。 话不多说,先上图

633833ffa1640fc0cb2cfc3615c299ff.png

这张图呢,就是全历史App中全古古迹功能的界面图。
显然,它通过沉浸状态栏、透明背景、recyclerView的自定义Item等,实现了一个很优秀的界面效果。
今天,我要做的就是猜测这效果背后的实现原理,并仿制一个类似的界面

正文

解构

我们先看看结构一下这效果背后的组成部分:

eabbb6c98c5162e3142cfe232c923b91.png

总的来讲,实现这样的效果主要需要四个部分:
    1.沉浸状态栏、透明toolbar与背景
    2.TabLayout样式自定义
    3.TextSwitcher文字切换(个人猜测是有可能是使用TextSwitcher实现的,实际今天的仿制里不涉及这个哈,今天的重点是沉浸与时间轴。如果这个也包含,篇幅就控制不住了......)
    4.ItemDecoration定制,绘图画出时间轴效果

实现

因为涉及到的内容比较多,所以只会放出部分关键的代码哈。
1.沉浸状态栏与透明背景
状态栏的沉浸涉及到不同版本、不同情景状态下的适配, 相对来讲比较复杂。想要研究的比较深的朋友,可以查看 https://www.jianshu.com/p/dc20e98b9a90 这篇博客, 内容充实,讲的也非常详细,本文的的沉浸适配也对其进行了参考。 我们今天涉及到的沉浸状态栏实际非常简单,准确的讲应该叫透明状态栏,也并不包含交互效果下状态栏的变换。 本文采用了透明主题的配置,如下所示:

API21 之后(也就是 android 5.0 之后)的状态栏,会默认覆盖一层半透明遮罩。且为了保持4.4以前系统正常使用,故需要三份 style 文件,即默认的values(不设置状态栏透明)、values-v19、values-v21(解决半透明遮罩问题)>

//valuse// values-v19。v19 开始有 android:windowTranslucentStatus 这个属性        true        true// values-v21。5.0 以上提供了 setStatusBarColor()  方法设置状态栏颜色。    false    true        @android:color/transparent
设置了透明主题之后,实际会布局会顶到状态栏, 因而需要为toolbar设置一个paddingTop
  /**     * 设置顶部statusBar与顶部View同背景     *     * @param activity 需要设置的activity     * @param topView  顶部需要设置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);                }            });        }    }
对应Acitivity设置:
StatusBarUtil.setStatusBarMergeWithTopView(this, tbHistory); //tbHistory为需要设置的透明toolbar,会在下面的布局中看到。 Acitivty布局文件:
<?xml version="1.0" encoding="utf-8"?>    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">                            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:>                                    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" />                                        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"/>                            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" /> 
这个布局文件除了Tablayout外并没有什么好讲的,所以我们直接进入第二部分
2.Tablayout自定义
全古迹中的tablayout主要就是进行了tab切换后字体的大小、粗细的变化,tab采用了滚动的模式,下划线Indicator是自定义的一个较短的下划线。
tablayout的下划线默认是与Tab等长的, 后来官方提供了app:tabIndicatorFullWidth="true" 这个属性来实现下划线与字体等长。但是显然,达不到图中的那种效果。
想要实现图中的效果,主要有两种方法,一种是通过反射拿到该控件来设置长度,一种是自定义一个tabView。
这里我们选择了第二种方式。
1.注意在tablayout布局文件中设置app:tabIndicatorHeight="0dp"。
2.编写自定义TabView布局文件。
<?xml version="1.0" encoding="utf-8"?>    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:layout_gravity="center"    android:orientation="horizontal">            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" />            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"/> 

在对应的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));//需要对应设置的tab标题            return tabView;        }
接下来就是设置viewPagerAdapter,以及监听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++) {//设置自定义tabView            View tabView = viewPagerAdapter.getTabView(i);            if(i==0){//默认先加载第一个,所以第一个的字体等样式先设置一下。                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) {                //未选中,则恢复样式                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();

直到这里,已经完成了大半部分喽,还剩最后的时间轴的绘制。

3.时间轴绘制
时间轴的绘制呢,我们需要借用设置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允许应用结合adapter的数据集,对特定的item添加绘制一个周边图案。可以用于给items之间添加分割线、高亮装饰效果或者分组边界等等。>
顾名思义,ItemDecoration就是对item起装饰作用,其最常见的用法就是添加分割线。
在这里的时间轴,需要画出圆环、圆点、线段、文字 。 完整代码如下:
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 dynasty;    private int divide_width = 66;    public TimeAxisItemDecoration(Context context, List 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();//为了动态获取        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);//圆环            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();//为了动态获取        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                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:是指当前Item的View对象        //RecyclerView parent:是指RecyclerView 本身        //RecyclerView.State state:通过State可以获取当前RecyclerView的状态,也可以通过State在RecyclerView各组件间传递参数    }}

成果

因为重点是沉浸和时间轴所以没有实现TextSwitcher的部分, 数据也是统一填充的(害 ,懒就一个字), 实际toolbar使用的是默认尺寸,toolbar高度再低点, 应该视觉效果会更好一点。

d4bd91034bbc7b024d41bf9acc4ae4d2.png

到这里就结束啦。 往期精彩回顾:
  • Android实现短信验证码自动填充功能

  • Android仿echo精美弹幕功能

  • Android实现头像重叠排列功能

  • Android仿QQ个性标签功能

  • Android仿QQ侧滑删除的功能

1aaacd528bdca0fa10bfdf2c2d50d7dc.png

5812a85d2963582ff2c9172a11753226.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值