TouTiao开源项目 分析笔记2

1.Constant常量定义类

1.1.源代码

public class Constant {

    public static final String USER_AGENT_MOBILE = "Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Mobile Safari/537.36";

    public static final String USER_AGENT_PC = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36";

    public static final int[] TAG_COLORS = new int[]{
            Color.parseColor("#90C5F0"),
            Color.parseColor("#91CED5"),
            Color.parseColor("#F88F55"),
            Color.parseColor("#C0AFD0"),
            Color.parseColor("#E78F8F"),
            Color.parseColor("#67CCB7"),
            Color.parseColor("#F6BC7E")
    };

    public static final int[] ICONS_DRAWABLES = new int[]{
            R.mipmap.ic_launcher_circle,
            R.mipmap.ic_launcher_rect,
            R.mipmap.ic_launcher_square};

    public static final String[] ICONS_TYPE = new String[]{"circle", "rect", "square"};

    public static final int SLIDABLE_DISABLE = 0;
    public static final int SLIDABLE_EDGE = 1;
    public static final int SLIDABLE_FULL = 2;

    public static final String AS = "as";
    public static final String CP = "cp";

    public static final int NEWS_CHANNEL_ENABLE = 1;
    public static final int NEWS_CHANNEL_DISABLE = 0;
}
View Code

 

1.2.作用介绍

  标签颜色存放成一个整型数组

  logo的三个不同形状,用整型数组来存放id

  图片类型用一个字符串数组来存放

  Slidable属性值用整型存放

  AS/CP,用字符串存放

  News_Channel属性,用整型存放 


2.开源项目material-dialogs的引用

2.1.用到了这种效果

  参考文章:开源项目material-dialogs的使用。

  

 

2.2.导入引用

  compile 'com.afollestad.material-dialogs:commons:0.9.4.4'

 

2.3.具体使用

  可以参考上面那篇文章。

  

  

  

  

  

  等效果,功能强大,具体代码,点击我。

 


3.ActivityManager深入理解

3.1.这里碰到了一个类ActivityManager.TaskDescription

  然后百度了一下:

  

 

3.2.其他用法可以参考下面这篇文章。

  ActivityManager总结:http://blog.csdn.net/lanye11/article/details/52221359


4.BaseActivity分析

4.1.getWindow().setNavigationBarColor

  不知道setNavigationBarColor是什么意思

  找到了这张图。

  

 

4.2.android.R.id.home

  

  就是左上角的图标,一般就是一个左箭头。

 

4.3.按返回键,逐个出栈的方法

 @Override
    public void onBackPressed() {
        // Fragment 逐个出栈
        int count = getSupportFragmentManager().getBackStackEntryCount();
        if (count == 0) {
            super.onBackPressed();
        } else {
            getSupportFragmentManager().popBackStack();
        }
    }

 

4.4.在清单中声明启动页活动的别名 

     <activity
            android:name=".SplashActivity"
            android:configChanges="orientation|screenSize|uiMode"
            android:label="@string/app_name"
            android:theme="@style/SplashTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
            </intent-filter>
        </activity>

        <activity-alias
            android:name=".SplashActivity_circle"
            android:enabled="true"
            android:icon="@mipmap/ic_launcher_circle"
            android:label="@string/app_name"
            android:targetActivity=".SplashActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity-alias>

        <activity-alias
            android:name=".SplashActivity_rect"
            android:enabled="false"
            android:icon="@mipmap/ic_launcher_rect"
            android:label="@string/app_name"
            android:targetActivity=".SplashActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity-alias>

        <activity-alias
            android:name=".SplashActivity_square"
            android:enabled="false"
            android:icon="@mipmap/ic_launcher_square"
            android:label="@string/app_name"
            android:targetActivity=".SplashActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity-alias>

  可以看到,这里使用了<activity-alias>标签

  这里有3个标签,代表着圆形,矩形,正方形的启动页。

  所以这里每当修改了应用图标之后,这里就可以动态地变化启动图标了。

  这里有一个activity,但是写了4个<intent-filter>

  所以这招很管用。

  注意:要把第一个<intent-filter>中将<category android:name="android...LAUNCHER">删去。

  参考文章:activity-alias详解及应用。

 

4.5.android禁用和开启启动图标

  这个和上面的修改应用图标结合使用。

  当修改了应用图标,应该停止当前应用的进程。

  所以要kill掉当前应用,而且桌面图标不能生效,直到应用图标修改完成,图标也换了,然后就可以点了。

 getPackageManager().setComponentEnabledSetting(new ComponentName(BaseActivity.this, getPackageName() + act),
                            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                            PackageManager.DONT_KILL_APP);

  参考文章:android禁用和开启四大组件的方法。

 

4.6.BaseActivity源代码

public class BaseActivity extends RxAppCompatActivity {
    private static final String TAG="BaseActivity";
    protected SlidrInterface slidrInterface;
    private int iconType=-1;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        //默认图标获取的是圆形circle
        this.iconType=SettingUtil.getInstance().getCustomIconValue();
        initSlidrable();
    }

    @Override
    protected void onResume(){
        super.onResume();
        //获取主题色
        int color=SettingUtil.getInstance().getColor();
        //获取图标形状,圆,矩,正
        int drawable=Constant.ICONS_DRAWABLES[SettingUtil.getInstance().getCustomIconValue()];
        if(getSupportActionBar()!=null){
            //设置标题栏的颜色
            getSupportActionBar().setBackgroundDrawable(new ColorDrawable(color));
        }
        //如果SDK版本>=21,还要设置状态栏的颜色
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP){
            getWindow().setStatusBarColor(CircleView.shiftColorDown(color));
            //TaskDescription==>用于在最近的任务列表中设置和检索当前活动的信息
            ActivityManager.TaskDescription taskDescription=new ActivityManager.TaskDescription(
                    getString(R.string.app_name),
                    BitmapFactory.decodeResource(getResources(),drawable),
                    color
            );
            setTaskDescription(taskDescription);
            //setNavigationBar是底部导航栏,模拟器上会有
            if(SettingUtil.getInstance().getNavBar()){
                getWindow().setNavigationBarColor(CircleView.shiftColorDown(color));
            }else{
                getWindow().setNavigationBarColor(Color.BLACK);
            }
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item){
        if(item.getItemId()==android.R.id.home){
            onBackPressed();
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onBackPressed(){
        //Fragment逐个出栈
        int count=getSupportFragmentManager().getBackStackEntryCount();
        if(count==0){
            super.onBackPressed();
        }else{
            getSupportFragmentManager().popBackStack();
        }
    }

    @Override
    protected void onStop(){

        //如果iconType不为默认的圆形了
        if(iconType!=SettingUtil.getInstance().getCustomIconValue()){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //前提是在清单中已经完成配置别名
                    String act=".SplashActivity_";
                    for(String s:Constant.ICONS_TYPE){
                        getPackageManager().setComponentEnabledSetting(new ComponentName(BaseActivity.this,getPackageName()+act+s),
                                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                                PackageManager.DONT_KILL_APP);
                    }

                    act+=Constant.ICONS_TYPE[SettingUtil.getInstance().getCustomIconValue()];

                    getPackageManager().setComponentEnabledSetting(new ComponentName(BaseActivity.this,getPackageName()+act),
                            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                            PackageManager.DONT_KILL_APP);
                }
            }).start();
        }

        super.onStop();
    }

    /**
     * 初始化Toolbar
     */
    protected void initToolbar(Toolbar toolbar, boolean homeAsUpEnabled, String title){
        toolbar.setTitle(title);
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(homeAsUpEnabled);
    }

    /**
     * 初始化滑动返回
     */
    protected void initSlidrable(){
        int isSlidable=SettingUtil.getInstance().getSlidable();
        if(isSlidable!= Constant.SLIDABLE_DISABLE){
            SlidrConfig config=new SlidrConfig.Builder()
                                    .edge(isSlidable==Constant.SLIDABLE_EDGE)
                                    .build();
            slidrInterface= Slidr.attach(this,config);
        }
    }

}
View Code


5.MainActivity分析

5.1.支持android材料设计,以及卡片设计模式

    implementation "com.android.support:appcompat-v7:${SUPPORT_LIBRARY_VERSION}"
    implementation "com.android.support:cardview-v7:${SUPPORT_LIBRARY_VERSION}"
    implementation "com.android.support:design:${SUPPORT_LIBRARY_VERSION}"

  在build.gradle中添加这些引用即可。

 

5.2.android:fitsSystemWIndows="true"

  这个View的所有padding属性失效

  可以用于沉浸式状态栏。

  参考文章:fitsSystemWindows属性讲解。

 

5.3.android:openDrawer

  这个在布局DrawerLayout中会用到。

  在布局中可以利用这个属性控制抽屉布局显示出来。

  参考文章:Android的Material Design初次尝试。

 

5.4.CoordinatorLayout有什么用

  CoordinatorLayout称为“super-powered FrameLayout”基本实现两个功能:

  1.作为顶层布局

  2.调度协调子布局

  参考文章:android CoordinatorLayout使用。

 

5.5.app:elevation="0dp"

  给控件,如AppBarLayout去掉阴影效果。

  参考文章:AppCompat Toolbar控件去掉阴影。

 

5.6.app:layout_scrollFlags=""

  给toolbar设置混动模式

  参考文章:Android详细分析AppBarLayout。

  app:popupTheme=""

  toolbar弹出菜单样式

  参考文章:app:popupTheme作用。 

 

5.7.app:layout_behavior=""

   android.support.design.widget.AppBarLayout$ScrollingViewBehavior

  这里定义了布局的行为,这是一个官方的行为

  参考这篇文章了解这个行为。

 

5.8.设置底部导航栏

 <android.support.design.widget.BottomNavigationView
            android:id="@+id/bottom_navigation"
            style="@style/Widget.Design.BottomNavigationView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_gravity="bottom"
            android:background="@color/viewBackground"
            app:elevation="16dp"
            app:itemIconTint="@drawable/nav_item_color_state"
            app:itemTextColor="@drawable/nav_item_color_state"
            app:layout_behavior="com.meiji.toutiao.widget.behavior.BottomNavigationBehavior"
            app:menu="@menu/bottom_navigation_main" />

  这里用了官方的一个BottomNavigationView,本例中就是底部导航栏的作用。

  app:itemIconTint来设置图标点击的颜色变化。

  app:itemTextColor来设置文字点击变化。

  这里需要有定义一个样式文件==>nav_item_color_state.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@color/Blue_Grey" android:state_checked="true"/>
    <item android:color="@color/textColorPrimary" android:state_checked="false"/>
</selector>

  然后还需要一个菜单布局文件:@menu/bottom_navigation_main

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_news"
        android:enabled="true"
        android:icon="@drawable/ic_newspaper_white_24dp"
        android:title="@string/title_news"
        app:showAsAction="ifRoom"/>
    <item
        android:id="@+id/action_photo"
        android:enabled="true"
        android:icon="@drawable/ic_gallery_white_24dp"
        android:title="@string/title_photo"
        app:showAsAction="ifRoom"/>
    <item
        android:id="@+id/action_video"
        android:enabled="true"
        android:icon="@drawable/ic_youtube_white_24dp"
        android:title="@string/title_video"
        app:showAsAction="ifRoom"/>
    <item
        android:id="@+id/action_media"
        android:enabled="true"
        android:icon="@drawable/ic_library_books_white_24dp"
        android:title="@string/title_media"
        app:showAsAction="ifRoom"/>
</menu>

  app:showAsAction的作用就是如果就空间就显示,如果没空间就在右边的三个点中。

 

5.9.左侧菜单栏

<android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header_main"
        app:menu="@menu/nav_menu"/>

  采用了NavigationView布局方式。

  左侧菜单栏的顶部利用app:headerLayout来嵌套布局。

  

5.10.布局运行预览

                      

 

5.11.如何禁用BottomNavigationView换挡模式?

  就是下方的图标是没有汉字的,如何才能图标和文字一起显示呢,写了这个类后,一行代码即可搞定。

  参考文章:

  https://stackoverflow.com/questions/40176244/how-to-disable-bottomnavigationview-shift-mode

  需要新建一个BottomNavigationView帮助类

public class BottomNavigationViewHelper {

    public static void disableShiftMode(BottomNavigationView view) {
        BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.getChildAt(0);
        try {
            Field shiftingMode = menuView.getClass().getDeclaredField("mShiftingMode");
            shiftingMode.setAccessible(true);
            shiftingMode.setBoolean(menuView, false);
            shiftingMode.setAccessible(false);
            for (int i = 0; i < menuView.getChildCount(); i++) {
                BottomNavigationItemView item = (BottomNavigationItemView) menuView.getChildAt(i);
                //noinspection RestrictedApi
                item.setShiftingMode(false);
                // set once again checked value, so view will be updated
                //noinspection RestrictedApi
                item.setChecked(item.getItemData().isChecked());
            }
        } catch (NoSuchFieldException e) {
            Log.e("BNVHelper", "Unable to get shift mode field", e);
        } catch (IllegalAccessException e) {
            Log.e("BNVHelper", "Unable to change value of shift mode", e);
        }
    }
}

  然后再调用一下这个函数即可。

 bottom_navigation = (BottomNavigationView) findViewById(R.id.bottom_navigation);
 BottomNavigationViewHelper.disableShiftMode(bottom_navigation);

 

5.12.标题栏左上角引导左侧菜单栏

  五行代码即可搞定。

     drawer_layout = findViewById(R.id.drawer_layout) as DrawerLayout
        val toggle = ActionBarDrawerToggle(
                this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
        drawer_layout!!.addDrawerListener(toggle)
        toggle.syncState()

 

5.13.解决toolbar.inflateMenu不生效的问题

  参考文章:http://blog.csdn.net/amd123456789/article/details/52474984

  

  其实上面只是为了测试

  真实情况还是要加,这个项目一定要重写下面这个函数,才有显示搜索图标

@Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_activity_main, menu);
        return true;
    }

 

 

5.14.使用第三方库taptargetview

             

  标题栏点击特效。

  参考文章:安卓taptargetview。

  高亮功能提示效果。

  implementation 'com.getkeepsafe.taptargetview:taptargetview:1.9.1'

 

5.15.利用TapTarget设置引导用户页面  

private void showTapTarget() {
        final Display display = getWindowManager().getDefaultDisplay();
        final Rect target = new Rect(
                0,
                display.getHeight(),
                0,
                display.getHeight());
        target.offset(display.getWidth() / 8, -56);

        // 引导用户使用
        TapTargetSequence sequence = new TapTargetSequence(this)
                .targets(
                        TapTarget.forToolbarMenuItem(toolbar, R.id.action_search, "点击这里进行搜索")
                                .dimColor(android.R.color.black)
                                .outerCircleColor(R.color.colorPrimary)
                                .drawShadow(true)
                                .id(1),
                        TapTarget.forToolbarNavigationIcon(toolbar, "点击这里展开侧栏")
                                .dimColor(android.R.color.black)
                                .outerCircleColor(R.color.colorPrimary)
                                .drawShadow(true)
                                .id(2),
                        TapTarget.forBounds(target, "点击这里切换新闻", "双击返回顶部\n再次双击刷新当前页面")
                                .dimColor(android.R.color.black)
                                .outerCircleColor(R.color.colorPrimary)
                                .targetRadius(60)
                                .transparentTarget(true)
                                .drawShadow(true)
                                .id(3)
                ).listener(new TapTargetSequence.Listener() {
                    @Override
                    public void onSequenceFinish() {
                        SettingUtil.getInstance().setIsFirstTime(false);
                    }

                    @Override
                    public void onSequenceStep(TapTarget lastTarget, boolean targetClicked) {

                    }

                    @Override
                    public void onSequenceCanceled(TapTarget lastTarget) {
                        SettingUtil.getInstance().setIsFirstTime(false);
                    }
                });
        sequence.start();
    }

 

 5.16.横竖屏切换保存数据==>重写onSaveInstanceState

 @Override
    protected void onSaveInstanceState(Bundle outState) {
        // recreate 时记录当前位置 (在 Manifest 已禁止 Activity 旋转,所以旋转屏幕并不会执行以下代码)
        outState.putInt(POSITION, position);
        outState.putInt(SELECT_ITEM, bottom_navigation.getSelectedItemId());
    }


6.目前效果

6.1.创建好了SplashActivity+MainActivity

  但是具体的Fragment一个都还没有实现。

  实现了一个左侧菜单+顶部标题栏+底部导航栏

 

6.2.GIF图示

  

  

 



posted @ 2017-12-01 10:18  Jason_Jan  阅读(903)  评论(0编辑  收藏  举报