系统应用闹钟之TabLayout的使用

此篇文章记录一些关于安卓原生应用闹钟里面TabLayout的使用,没见过用这个组件的源代码,想从它里面学习学习。就当是见见世面,顺便写个笔记。

先从印象到布局

原生闹钟给我的印象非常好,有种说不出的美感。在改工作上的问题时,顺便看它的样式,大致就是一个TabLayout加上四个fragment

首先是头部的四个图标,是一个嵌在了Toolbar中的TabLayoutToolbar也是一个ViewGroup,在它的里面放一个其他的控件也是理所当然,只是以前没试过。第一次看到这个写法,感觉非常好。

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
app:elevation="0dp">
<android.support.v7.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:contentInsetStart="0dp"
    tools:ignore="RtlSymmetry">
    <android.support.design.widget.TabLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:tabGravity="fill"
        app:tabIndicatorColor="@android:color/transparent"
        app:tabMaxWidth="0dp"
        app:tabMode="fixed"
        app:tabPaddingEnd="0dp"
        app:tabPaddingStart="0dp" />
</android.support.v7.widget.Toolbar>

然后在Toolbar下面就是一个ViewPager,存放在一个FrameLayout中。

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <android.support.v4.view.ViewPager
        android:id="@+id/desk_clock_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:importantForAccessibility="no"
        android:saveEnabled="false" />

    <include layout="@layout/drop_shadow" />

</FrameLayout>

这样基本上大致的框架就出来了。接着看对它的初始化: 基本步骤和网上的教程是差不多的,只是其中的UiDataModel感觉非常有意思,感觉它几乎无所不能,用到的任何东西都可以往里面取。

// Create the tabs that make up the user interface.
mTabLayout = (TabLayout) findViewById(R.id.tabs);
final int tabCount = UiDataModel.getUiDataModel().getTabCount();
final boolean showTabLabel = getResources().getBoolean(R.bool.showTabLabel);
final boolean showTabHorizontally = getResources().getBoolean(R.bool.showTabHorizontally);
for (int i = 0; i < tabCount; i++) {
    final UiDataModel.Tab tabModel = UiDataModel.getUiDataModel().getTab(i);
    final @StringRes int labelResId = tabModel.getLabelResId();

    final TabLayout.Tab tab = mTabLayout.newTab()
            .setTag(tabModel)
            .setIcon(tabModel.getIconResId())
            .setContentDescription(labelResId);

    if (showTabLabel) {
        tab.setText(labelResId);
        tab.setCustomView(R.layout.tab_item);

        @SuppressWarnings("ConstantConditions")
        final TextView text = (TextView) tab.getCustomView()
                .findViewById(android.R.id.text1);
        text.setTextColor(mTabLayout.getTabTextColors());

        // Bind the icon to the TextView.
        final Drawable icon = tab.getIcon();
        if (showTabHorizontally) {
            // Remove the icon so it doesn't affect the minimum TabLayout height.
            tab.setIcon(null);
            text.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
        } else {
            text.setCompoundDrawablesRelativeWithIntrinsicBounds(null, icon, null, null);
        }
    }

打开UiDataModel,第一个引起我的关注的是一个枚举型,在其中保存了有关Tab的基本信息,如文本、图标资源id以及该Tab所对应的fragment等。第一次见到这种写法,有点震惊,同时觉得对于这种静态的应用,用枚举型去保存也符合常理,我觉得这种方法可以以后利用在我写的Android项目中,比如说我的应用中,Tab也是固定的,同时这样写可以让那些信息更加集中,修改起来更加方便,让代码的逻辑性更强。看代码:

/** Identifies each of the primary tabs within the application. */
public enum Tab {
    ALARMS(AlarmClockFragment.class, R.drawable.ic_tab_alarm, R.string.menu_alarm),
    CLOCKS(ClockFragment.class, R.drawable.ic_tab_clock, R.string.menu_clock),
    TIMERS(TimerFragment.class, R.drawable.ic_tab_timer, R.string.menu_timer),
    STOPWATCH(StopwatchFragment.class, R.drawable.ic_tab_stopwatch, R.string.menu_stopwatch);

    private final String mFragmentClassName;
    private final @DrawableRes int mIconResId;
    private final @StringRes int mLabelResId;

    Tab(Class fragmentClass, @DrawableRes int iconResId, @StringRes int labelResId) {
        mFragmentClassName = fragmentClass.getName();
        mIconResId = iconResId;
        mLabelResId = labelResId;
    }

    public String getFragmentClassName() { return mFragmentClassName; }
    public @DrawableRes int getIconResId() { return mIconResId; }
    public @StringRes int getLabelResId() { return mLabelResId; }
}

感觉自己简直被这源代码所陶醉。后面继续看关于TabLayout的初始化。这其中,对其的适配器的写法,我也是比较感兴趣的。因为自己在写应用的时候,遇到过关于PagerAdapter的问题,所以比较期待它是如何去实现的。

// Customize the view pager.
mFragmentTabPagerAdapter = new FragmentTabPagerAdapter(this);
mFragmentTabPager = (ViewPager) findViewById(R.id.desk_clock_pager);
// Keep all four tabs to minimize jank.
mFragmentTabPager.setOffscreenPageLimit(3);
// Set Accessibility Delegate to null so view pager doesn't intercept movements and
// prevent the fab from being selected.
mFragmentTabPager.setAccessibilityDelegate(null);
// Mirror changes made to the selected page of the view pager into UiDataModel.
mFragmentTabPager.addOnPageChangeListener(new PageChangeWatcher());
mFragmentTabPager.setAdapter(mFragmentTabPagerAdapter);

// Mirror changes made to the selected tab into UiDataModel.
mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
    @Override
    public void onTabSelected(TabLayout.Tab tab) {
        UiDataModel.getUiDataModel().setSelectedTab((UiDataModel.Tab) tab.getTag());
    }

    @Override
    public void onTabUnselected(TabLayout.Tab tab) {
    }

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

适配器的源代码不完全贴出来,感觉都对不起这源代码。它继承自ViewPager,自己实现了其中的方法,具体如下:

/**
 * This adapter produces the DeskClockFragments that are the content of the DeskClock tabs. The
 * adapter presents the tabs in LTR and RTL order depending on the text layout direction for the
 * current locale. To prevent issues when switching between LTR and RTL, fragments are registered
 * with the manager using position-independent tags, which is an important departure from
 * FragmentPagerAdapter.
 */
final class FragmentTabPagerAdapter extends PagerAdapter {

    private final DeskClock mDeskClock;

    /** The manager into which fragments are added. */
    private final FragmentManager mFragmentManager;

    /** A fragment cache that can be accessed before {@link #instantiateItem} is called. */
    private final Map<UiDataModel.Tab, DeskClockFragment> mFragmentCache;

    /** The active fragment transaction if one exists. */
    private FragmentTransaction mCurrentTransaction;

    /** The current fragment displayed to the user. */
    private Fragment mCurrentPrimaryItem;

    FragmentTabPagerAdapter(DeskClock deskClock) {
        mDeskClock = deskClock;
        mFragmentCache = new ArrayMap<>(getCount());
        mFragmentManager = deskClock.getFragmentManager();
    }

    @Override
    public int getCount() {
        return UiDataModel.getUiDataModel().getTabCount();
    }

    /**
     * @param position the left-to-right index of the fragment to be returned
     * @return the fragment displayed at the given {@code position}
     */
    DeskClockFragment getDeskClockFragment(int position) {
        // Fetch the tab the UiDataModel reports for the position.
        final UiDataModel.Tab tab = UiDataModel.getUiDataModel().getTabAt(position);

        // First check the local cache for the fragment.
        DeskClockFragment fragment = mFragmentCache.get(tab);
        if (fragment != null) {
            return fragment;
        }

        // Next check the fragment manager; relevant when app is rebuilt after locale changes
        // because this adapter will be new and mFragmentCache will be empty, but the fragment
        // manager will retain the Fragments built on original application launch.
        fragment = (DeskClockFragment) mFragmentManager.findFragmentByTag(tab.name());
        if (fragment != null) {
            fragment.setFabContainer(mDeskClock);
            mFragmentCache.put(tab, fragment);
            return fragment;
        }

        // Otherwise, build the fragment from scratch.
        final String fragmentClassName = tab.getFragmentClassName();
        fragment = (DeskClockFragment) Fragment.instantiate(mDeskClock, fragmentClassName);
        fragment.setFabContainer(mDeskClock);
        mFragmentCache.put(tab, fragment);
        return fragment;
    }

    @Override
    public void startUpdate(ViewGroup container) {
        if (container.getId() == View.NO_ID) {
            throw new IllegalStateException("ViewPager with adapter " + this + " has no id");
        }
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        if (mCurrentTransaction == null) {
            mCurrentTransaction = mFragmentManager.beginTransaction();
        }

        // Use the fragment located in the fragment manager if one exists.
        final UiDataModel.Tab tab = UiDataModel.getUiDataModel().getTabAt(position);
        Fragment fragment = mFragmentManager.findFragmentByTag(tab.name());
        if (fragment != null) {
            mCurrentTransaction.attach(fragment);
        } else {
            fragment = getDeskClockFragment(position);
            mCurrentTransaction.add(container.getId(), fragment, tab.name());
        }

        if (fragment != mCurrentPrimaryItem) {
            FragmentCompat.setMenuVisibility(fragment, false);
            FragmentCompat.setUserVisibleHint(fragment, false);
        }

        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        if (mCurrentTransaction == null) {
            mCurrentTransaction = mFragmentManager.beginTransaction();
        }
        final DeskClockFragment fragment = (DeskClockFragment) object;
        fragment.setFabContainer(null);
        mCurrentTransaction.detach(fragment);
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        final Fragment fragment = (Fragment) object;
        if (fragment != mCurrentPrimaryItem) {
            if (mCurrentPrimaryItem != null) {
                FragmentCompat.setMenuVisibility(mCurrentPrimaryItem, false);
                FragmentCompat.setUserVisibleHint(mCurrentPrimaryItem, false);
            }
            if (fragment != null) {
                FragmentCompat.setMenuVisibility(fragment, true);
                FragmentCompat.setUserVisibleHint(fragment, true);
            }
            mCurrentPrimaryItem = fragment;
        }
    }

    @Override
    public void finishUpdate(ViewGroup container) {
        if (mCurrentTransaction != null) {
            mCurrentTransaction.commitAllowingStateLoss();
            mCurrentTransaction = null;
            mFragmentManager.executePendingTransactions();
        }
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return ((Fragment) object).getView() == view;
    }
}

Read more

Volcano 与 Kubernetes GPU 调度学习笔记

本笔记系统整理 Volcano 调度器、Kubernetes 调度框架、GPU Device Plugin、HAMi 等云原生 AI 调度领域的核心知识,适合用于学习、复习和工程实践参考。 目录 * 第一部分:Volcano 入门 * 1. Volcano 是什么 * 2. 安装与快速使用 * 3. 核心特性一览 * 第二部分:Volcano 整体架构 * 4. Volcano 解决的核心问题 * 5. 整体架构与数据流 * 6. 三层抽象模型 * 第三部分:Volcano 核心实现原理 * 7. Session 机制 * 8. Gang Scheduling 实现 * 9. Queue 与 DRF 公平调度

容器镜像(4):镜像的常用工具箱

容器镜像(4):镜像的常用工具箱

前几篇在讲多架构镜像时已经用过 skopeo 和 crane 做镜像复制,这篇系统整理这两个工具的完整能力,同时介绍几个日常操作镜像时同样好用的工具。 一、skopeo:不依赖 Daemon 的镜像瑞士军刀 skopeo 的核心价值是绕过 Docker daemon,直接与 Registry API 交互。上一篇用它做镜像复制和离线传输,但它的能力远不止于此。 1.1 安装 # Ubuntu / Debian sudo apt install -y skopeo skopeo --version # skopeo version 1.15.1 1.2 inspect:免拉取检查镜像元数据 docker inspect 需要先把镜像拉到本地,skopeo inspect 直接向 Registry

容器镜像(3):多架构镜像构建

容器镜像(3):多架构镜像构建

一、什么是多架构镜像 1.1 OCI Image Index 上一篇介绍了单平台镜像的结构:一个 Manifest 指向 Config 和若干 Layer blob。多架构镜像在此之上多了一层——OCI Image Index(也叫 Manifest List),是一个轻量的索引文件,把多个单平台 Manifest 组织在一起: $ docker manifest inspect golang:1.22-alpine { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests&

容器镜像(2):containerd 视角下的镜像

容器镜像(2):containerd 视角下的镜像

一、为什么需要了解 containerd 如果你只用 docker run 跑容器,从来不关心底层,那可以不了解 containerd。但如果你在用 Kubernetes,或者想真正理解"容器运行时"是什么,containerd 是绕不开的。 事实上,当你执行 docker run 的时候,containerd 早就在后台悄悄工作了——Docker 从 1.11 版本开始,就把核心运行时剥离出来交给 containerd 负责。 1.1 Docker 的架构演变 早期的 Docker(1.10 及之前)是一个"大一统"的单体程序:一个 dockerd