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; }
1.2.作用介绍
标签颜色存放成一个整型数组
logo的三个不同形状,用整型数组来存放id
图片类型用一个字符串数组来存放
Slidable属性值用整型存放
AS/CP,用字符串存放
News_Channel属性,用整型存放
2.开源项目material-dialogs的引用
2.1.用到了这种效果
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">删去。
4.5.android禁用和开启启动图标
这个和上面的修改应用图标结合使用。
当修改了应用图标,应该停止当前应用的进程。
所以要kill掉当前应用,而且桌面图标不能生效,直到应用图标修改完成,图标也换了,然后就可以点了。
getPackageManager().setComponentEnabledSetting(new ComponentName(BaseActivity.this, getPackageName() + act), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
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); } } }
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属性失效
可以用于沉浸式状态栏。
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去掉阴影效果。
5.6.app:layout_scrollFlags=""
给toolbar设置混动模式
app:popupTheme=""
toolbar弹出菜单样式
5.7.app:layout_behavior=""
这里定义了布局的行为,这是一个官方的行为
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
标题栏点击特效。
高亮功能提示效果。
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图示