Diycode开源项目 MainActivity分析
1.分析MainActivity整体结构
1.1.首先看一下这个界面的整体效果。
1.2.活动源代码如下
/*
* Copyright 2017 GcsSloop
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Last modified 2017-03-08 01:01:18
*
* GitHub: https://github.com/GcsSloop
* Website: http://www.gcssloop.com
* Weibo: http://weibo.com/GcsSloop
*/ package com.gcssloop.diycode.activity; import android.support.design.widget.NavigationView;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.Toolbar;
import android.view.GestureDetector;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView; import com.bumptech.glide.Glide;
import com.gcssloop.diycode.R;
import com.gcssloop.diycode.base.app.BaseActivity;
import com.gcssloop.diycode.base.app.ViewHolder;
import com.gcssloop.diycode.fragment.NewsListFragment;
import com.gcssloop.diycode.fragment.SitesListFragment;
import com.gcssloop.diycode.fragment.TopicListFragment;
import com.gcssloop.diycode.test.TextFragment;
import com.gcssloop.diycode.utils.Config;
import com.gcssloop.diycode.utils.DataCache;
import com.gcssloop.diycode_sdk.api.login.event.LogoutEvent;
import com.gcssloop.diycode_sdk.api.user.bean.User;
import com.gcssloop.diycode_sdk.api.user.bean.UserDetail;
import com.gcssloop.diycode_sdk.api.user.event.GetMeEvent;
import com.gcssloop.diycode_sdk.log.Logger; import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode; public class MainActivity extends BaseActivity
implements NavigationView.OnNavigationItemSelectedListener, View.OnClickListener {
private DataCache mCache;
private Config mConfig;
private int mCurrentPosition = 0;
private TopicListFragment mFragment1;
private NewsListFragment mFragment2;
private SitesListFragment mFragment3; private boolean isToolbarFirstClick = true; @Override
public int getLayoutId() {
return R.layout.activity_main;
} @Override
public void initViews(ViewHolder holder, View root) {
EventBus.getDefault().register(this);
mCache = new DataCache(this);
mConfig = Config.getSingleInstance();
initMenu(holder);
initViewPager(holder);
} //--- viewpager adapter ------------------------------------------------------------------------ private void initViewPager(ViewHolder holder) {
ViewPager mViewPager = holder.get(R.id.view_pager);
TabLayout mTabLayout = holder.get(R.id.tab_layout);
mViewPager.setOffscreenPageLimit(3); // 防止滑动到第三个页面时,第一个页面被销毁 mFragment1 = TopicListFragment.newInstance();
mFragment2 = NewsListFragment.newInstance();
mFragment3 = SitesListFragment.newInstance(); mViewPager.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
String[] types = {"Topics", "News", "Sites", "Test"}; @Override
public Fragment getItem(int position) {
if (position == 0)
return mFragment1;
if (position == 1)
return mFragment2;
if (position == 2)
return mFragment3;
return TextFragment.newInstance(types[position]);
} @Override
public int getCount() {
return 3;
} @Override
public CharSequence getPageTitle(int position) {
return types[position];
}
}); mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
} @Override
public void onPageSelected(int position) {
mCurrentPosition = position;
} @Override
public void onPageScrollStateChanged(int state) {
}
}); mCurrentPosition = mConfig.getMainViewPagerPosition();
mViewPager.setCurrentItem(mCurrentPosition); mTabLayout.setupWithViewPager(mViewPager);
} // 快速返回顶部
private void quickToTop() {
switch (mCurrentPosition) {
case 0:
mFragment1.quickToTop();
break;
case 1:
mFragment2.quickToTop();
break;
case 2:
mFragment3.quickToTop();
break;
}
} // 如果收到此状态说明用户已经登录成功了
@Subscribe(threadMode = ThreadMode.MAIN)
public void onLogin(GetMeEvent event) {
if (event.isOk()) {
UserDetail me = event.getBean();
mCache.saveMe(me);
loadMenuData(); // 加载菜单数据
}
} // 如果收到此状态说明用户登出了
@Subscribe(threadMode = ThreadMode.MAIN)
public void onLogout(LogoutEvent event) {
loadMenuData(); // 加载菜单数据
} //--- menu ------------------------------------------------------------------------------------- // 初始化菜单(包括侧边栏菜单和顶部菜单选项)
private void initMenu(ViewHolder holder) {
Toolbar toolbar = holder.get(R.id.toolbar);
toolbar.setLogo(R.mipmap.logo_actionbar);
toolbar.setTitle("");
DrawerLayout drawer = holder.get(R.id.drawer_layout);
setSupportActionBar(toolbar);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.setDrawerListener(toggle);
toggle.syncState();
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this); // 双击 666
final GestureDetector detector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
quickToTop(); // 快速返回头部
return super.onDoubleTap(e);
}
}); toolbar.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
detector.onTouchEvent(event);
return false;
}
}); toolbar.setOnClickListener(this); holder.setOnClickListener(this, R.id.fab); loadMenuData();
} // 加载侧边栏菜单数据(与用户相关的)
private void loadMenuData() {
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
View headerView = navigationView.getHeaderView(0);
ImageView avatar = (ImageView) headerView.findViewById(R.id.nav_header_image);
TextView username = (TextView) headerView.findViewById(R.id.nav_header_name);
TextView tagline = (TextView) headerView.findViewById(R.id.nav_header_tagline); if (mDiycode.isLogin()) {
UserDetail me = mCache.getMe();
if (me == null) {
Logger.e("获取自己缓存失效");
mDiycode.getMe(); // 重新加载
return;
} username.setText(me.getLogin());
tagline.setText(me.getTagline());
Glide.with(this).load(me.getAvatar_url()).into(avatar);
avatar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
UserDetail me = mCache.getMe();
if (me == null) {
try {
me = mDiycode.getMeNow();
} catch (Exception e) {
e.printStackTrace();
}
} if (me != null) {
User user = new User();
user.setId(me.getId());
user.setName(me.getName());
user.setLogin(me.getLogin());
user.setAvatar_url(me.getAvatar_url());
UserActivity.newInstance(MainActivity.this, user);
}
}
});
} else {
mCache.removeMe();
username.setText("(未登录)");
tagline.setText("点击头像登录");
avatar.setImageResource(R.mipmap.ic_launcher);
avatar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openActivity(LoginActivity.class);
}
});
}
} @Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
} @Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
} @Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
openActivity(SettingActivity.class);
return true;
} else if (id == R.id.action_notification) {
if (!mDiycode.isLogin()) {
openActivity(LoginActivity.class);
} else {
openActivity(NotificationActivity.class);
}
return true;
}
return super.onOptionsItemSelected(item);
} @Override
public boolean onNavigationItemSelected(MenuItem item) {
int id = item.getItemId(); if (id == R.id.nav_post) {
if (!mDiycode.isLogin()) {
openActivity(LoginActivity.class);
return true;
}
MyTopicActivity.newInstance(this, MyTopicActivity.InfoType.MY_TOPIC);
} else if (id == R.id.nav_collect) {
if (!mDiycode.isLogin()) {
openActivity(LoginActivity.class);
return true;
}
MyTopicActivity.newInstance(this, MyTopicActivity.InfoType.MY_COLLECT);
} else if (id == R.id.nav_about) {
openActivity(AboutActivity.class);
} else if (id == R.id.nav_setting) {
openActivity(SettingActivity.class);
} DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
} @Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
mConfig.saveMainViewPagerPosition(mCurrentPosition);
} @Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.toolbar:
if (isToolbarFirstClick) {
toastShort("双击标题栏快速返回顶部");
isToolbarFirstClick = false;
}
break;
case R.id.fab:
quickToTop();
break;
}
}
}
1.3.布局源代码
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2017 GcsSloop
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~
~ Last modified 2017-03-08 01:01:18
~
~ GitHub: https://github.com/GcsSloop
~ Website: http://www.gcssloop.com
~ Weibo: http://weibo.com/GcsSloop
--> <android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start"> <include
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent"/> <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/activity_main_drawer"/> </android.support.v4.widget.DrawerLayout>
1.4.对应关系
- MainActivity的总体布局==>activity_main.xml
- 主活动布局中的include布局==>app_bar_main.xml
- content_main.xml
2.Android布局的参考项
2.1.首先是DrawrLayout。
它包括了一个侧滑的效果,当然这个具体侧滑菜单还要自己定义。
参考文章:android官方侧滑菜单DrawerLayout详解。
2.2.然后是具体的NavigationView。侧滑菜单具体定义。
参考文章:Android5.0之NavigationView的使用。
2.3.然后是主页面的一个CoordinatorLayout。
作用:实现浮动按钮的滚动效果。
2.4.然后是AppBarLayout。
作用就是一些动画效果吧。上滑隐藏了标题栏。
就像下图这样:
2.5.然后是TabLayout。
作用:就是添加一个导航栏,切换后ViewPager也随之切换。
类似于这样的效果:
2.6.然后是浮动按钮==>FloatingActionButton。
这种东西:
3.MainActivity定义的变量
3.1.首先预览一下所有变量。
可以看到,MainActivity继承了BaseActivity。为了少写一些每个页面都会有的标题栏,获取布局id等。
然后实现了NavigationView的监听器==>上面的导航菜单,左右滑动可以切换页面。
3.2.private DataCache mCache.
DataCache类定义在通用包中,数据缓存工具。
3.2.1.参考一下源代码。
/*
* Copyright 2017 GcsSloop
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Last modified 2017-03-12 02:50:16
*
* GitHub: https://github.com/GcsSloop
* Website: http://www.gcssloop.com
* Weibo: http://weibo.com/GcsSloop
*/ package com.gcssloop.diycode.utils; import android.content.Context;
import android.support.annotation.NonNull;
import android.util.LruCache; import com.gcssloop.diycode_sdk.api.news.bean.New;
import com.gcssloop.diycode_sdk.api.sites.bean.Sites;
import com.gcssloop.diycode_sdk.api.topic.bean.TopicContent;
import com.gcssloop.diycode_sdk.api.topic.bean.TopicReply;
import com.gcssloop.diycode_sdk.api.user.bean.UserDetail;
import com.gcssloop.diycode_sdk.utils.ACache; import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List; /**
* 数据缓存工具
*/
public class DataCache {
private static int M = 1024 * 1024;
ACache mDiskCache;
LruCache<String, Object> mLruCache; public DataCache(Context context) {
mDiskCache = ACache.get(new File(FileUtil.getExternalCacheDir(context.getApplicationContext(), "diy-data")));
mLruCache = new LruCache<>(5 * M);
} public <T extends Serializable> void saveListData(String key, List<T> data) {
ArrayList<T> datas = (ArrayList<T>) data;
mLruCache.put(key, datas);
mDiskCache.put(key, datas, ACache.TIME_WEEK); // 数据缓存时间为 1 周
} public <T extends Serializable> void saveData(@NonNull String key, @NonNull T data) {
mLruCache.put(key, data);
mDiskCache.put(key, data, ACache.TIME_WEEK); // 数据缓存时间为 1 周
} public <T extends Serializable> T getData(@NonNull String key) {
T result = (T) mLruCache.get(key);
if (result == null) {
result = (T) mDiskCache.getAsObject(key);
if (result != null) {
mLruCache.put(key, result);
}
}
return result;
} public void removeDate(String key) {
mDiskCache.remove(key);
} public void saveTopicContent(TopicContent content) {
saveData("topic_content_" + content.getId(), content);
String preview = HtmlUtil.Html2Text(content.getBody_html());
if (preview.length() > 100) {
preview = preview.substring(0, 100);
}
saveData("topic_content_preview" + content.getId(), preview);
} public TopicContent getTopicContent(int id) {
return getData("topic_content_" + id);
} public String getTopicPreview(int id) {
String key = "topic_content_preview" + id;
return getData(key);
} public void saveTopicRepliesList(int topic_id, List<TopicReply> replyList) {
ArrayList<TopicReply> replies = new ArrayList<>(replyList);
saveData("topic_reply_" + topic_id, replies);
} public List<TopicReply> getTopicRepliesList(int topic_id) {
return getData("topic_reply_" + topic_id);
} public void saveTopicsListObj(List<Object> topicList) {
ArrayList<Object> topics = new ArrayList<>(topicList);
saveData("topic_list_obj_", topics);
} public List<Object> getTopicsListObj() {
return getData("topic_list_obj_");
} public void saveNewsList(List<New> newList) {
ArrayList<New> news = new ArrayList<>(newList);
saveData("news_list_", news);
} public List<New> getNewsList() {
return getData("news_list_");
} public void saveNewsListObj(List<Object> newList) {
ArrayList<Object> news = new ArrayList<>(newList);
saveData("news_list_obj_", news);
} public List<Object> getNewsListObj() {
return getData("news_list_obj_");
} public void saveMe(UserDetail user) {
saveData("Gcs_Me_", user);
} public UserDetail getMe() {
return getData("Gcs_Me_");
} public void removeMe() {
removeDate("Gcs_Me_");
} public void saveSites(List<Sites> sitesList) {
saveListData("sites_", sitesList);
} public List<Sites> getSites() {
return getData("sites_");
} public <T extends Serializable> void saveSitesItems(List<T> sitesList) {
saveListData("sites_item_", sitesList);
} public <T extends Serializable> ArrayList<T> getSitesItems() {
return getData("sites_item_");
}
}
3.2.2.看一下DataCache中定义的变量。
这里定义的M是一兆缓存单位的意思。
Acache是定义在sdk中处理缓存的类。
LruCache<String,Object>是Android提供的缓存工具类。
3.2.3.看一下DataCache构造函数。
因为这里又用了一个通用类FileUtil。限于文章篇幅,这里就不写了,今后也会用到。
3.2.4.将数据缓存一周(保留List或者T),得到数据,移除数据。
3.2.5.保存话题内容,保存话题回复。保存话题列表。
3.2.6.保存News列表,保存我的页面数据,保存网站数据。
3.3.private Config mConfig;
作用:用户设置。
3.3.1.源代码如下:
/*
* Copyright 2017 GcsSloop
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Last modified 2017-03-28 04:48:02
*
* GitHub: https://github.com/GcsSloop
* Website: http://www.gcssloop.com
* Weibo: http://weibo.com/GcsSloop
*/ package com.gcssloop.diycode.utils; import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.LruCache; import com.gcssloop.diycode_sdk.utils.ACache; import java.io.Serializable; /**
* 用户设置
*/
public class Config {
private static int M = 1024 * 1024;
private volatile static Config mConfig;
private static LruCache<String, Object> mLruCache = new LruCache<>(1 * M);
private static ACache mDiskCache; private Config(Context context) {
mDiskCache = ACache.get(context, "config");
} public static Config init(Context context) {
if (null == mConfig) {
synchronized (Config.class) {
if (null == mConfig) {
mConfig = new Config(context);
}
}
}
return mConfig;
} public static Config getSingleInstance() {
return mConfig;
} //--- 基础 ----------------------------------------------------------------------------------- public <T extends Serializable> void saveData(@NonNull String key, @NonNull T value) {
mLruCache.put(key, value);
mDiskCache.put(key, value);
} public <T extends Serializable> T getData(@NonNull String key, @Nullable T defaultValue) {
T result = (T) mLruCache.get(key);
if (result != null) {
return result;
}
result = (T) mDiskCache.getAsObject(key);
if (result != null) {
mLruCache.put(key, result);
return result;
}
return defaultValue;
} //--- 浏览器 --------------------------------------------------------------------------------- private static String Key_Browser = "UseInsideBrowser_"; public void setUesInsideBrowser(@NonNull Boolean bool) {
saveData(Key_Browser, bool);
} public Boolean isUseInsideBrowser() {
return getData(Key_Browser, Boolean.TRUE);
} //--- 首页状态 ------------------------------------------------------------------------------- private String Key_MainViewPager_Position = "Key_MainViewPager_Position"; public void saveMainViewPagerPosition(Integer position) {
mLruCache.put(Key_MainViewPager_Position, position);
} public Integer getMainViewPagerPosition() {
return getData(Key_MainViewPager_Position, 0);
} //--- Topic状态 ------------------------------------------------------------------------------ private String Key_TopicList_LastPosition = "Key_TopicList_LastPosition";
private String Key_TopicList_LastOffset = "Key_TopicList_LastOffset"; public void saveTopicListState(Integer lastPosition, Integer lastOffset) {
saveData(Key_TopicList_LastPosition, lastPosition);
saveData(Key_TopicList_LastOffset, lastOffset);
} public Integer getTopicListLastPosition() {
return getData(Key_TopicList_LastPosition, 0);
} public Integer getTopicListLastOffset() {
return getData(Key_TopicList_LastOffset, 0);
} private String Key_TopicList_PageIndex = "Key_TopicList_PageIndex"; public void saveTopicListPageIndex(Integer pageIndex) {
saveData(Key_TopicList_PageIndex, pageIndex);
} public Integer getTopicListPageIndex() {
return getData(Key_TopicList_PageIndex, 0);
} //--- News状态 ------------------------------------------------------------------------------ private String Key_NewsList_LastScroll = "Key_NewsList_LastScroll"; public void saveNewsListScroll(Integer lastScrollY) {
saveData(Key_NewsList_LastScroll, lastScrollY);
} public Integer getNewsLastScroll() {
return getData(Key_NewsList_LastScroll, 0);
} private String Key_NewsList_LastPosition = "Key_NewsList_LastPosition"; public void saveNewsListPosition(Integer lastPosition) {
saveData(Key_NewsList_LastPosition, lastPosition);
} public Integer getNewsListLastPosition() {
return getData(Key_NewsList_LastPosition, 0);
} private String Key_NewsList_PageIndex = "Key_NewsList_PageIndex"; public void saveNewsListPageIndex(Integer pageIndex) {
saveData(Key_NewsList_PageIndex, pageIndex);
} public Integer getNewsListPageIndex() {
return getData(Key_NewsList_PageIndex, 0);
}
}
3.3.2.预览一下变量。
首先是M单位定义的大小。
然后是用了一个关键字:volatile。
3.3.3.构造函数。
从Acache中获得API中处理缓存的类。
3.3.4.初始化获得静态配置Config。
3.3.5.获得单例。
3.3.6.Config中配置保存数据和获得数据。
3.3.7.Config中配置浏览器
3.3.8.Config中配置首页状态。
默认从第0页开始。
3.3.9.Config中配置Topic状态。
3.3.10.Config中配置News状态。
3.4.private int mCurrentPosition=0;
约定第一个viewPager是从话题开始。
3.5.private TopicListFragment mFragment1;
话题页面碎片。
源代码如下:
/*
* Copyright 2017 GcsSloop
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Last modified 2017-04-08 23:15:33
*
* GitHub: https://github.com/GcsSloop
* Website: http://www.gcssloop.com
* Weibo: http://weibo.com/GcsSloop
*/ package com.gcssloop.diycode.fragment; import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.View; import com.gcssloop.diycode.fragment.base.SimpleRefreshRecyclerFragment;
import com.gcssloop.diycode.fragment.provider.TopicProvider;
import com.gcssloop.diycode_sdk.api.topic.bean.Topic;
import com.gcssloop.diycode_sdk.api.topic.event.GetTopicsListEvent;
import com.gcssloop.diycode_sdk.log.Logger;
import com.gcssloop.recyclerview.adapter.multitype.HeaderFooterAdapter; import java.util.List; /**
* 首页 topic 列表
*/
public class TopicListFragment extends SimpleRefreshRecyclerFragment<Topic, GetTopicsListEvent> { private boolean isFirstLaunch = true; public static TopicListFragment newInstance() {
Bundle args = new Bundle();
TopicListFragment fragment = new TopicListFragment();
fragment.setArguments(args);
return fragment;
} @Override public void initData(HeaderFooterAdapter adapter) {
// 优先从缓存中获取数据,如果是第一次加载则恢复滚动位置,如果没有缓存则从网络加载
List<Object> topics = mDataCache.getTopicsListObj();
if (null != topics && topics.size() > 0) {
Logger.e("topics : " + topics.size());
pageIndex = mConfig.getTopicListPageIndex();
adapter.addDatas(topics);
if (isFirstLaunch) {
int lastPosition = mConfig.getTopicListLastPosition();
mRecyclerView.getLayoutManager().scrollToPosition(lastPosition);
isFirstAddFooter = false;
isFirstLaunch = false;
}
} else {
loadMore();
}
} @Override protected void setAdapterRegister(Context context, RecyclerView recyclerView,
HeaderFooterAdapter adapter) {
adapter.register(Topic.class, new TopicProvider(getContext()));
} @NonNull @Override protected String request(int offset, int limit) {
return mDiycode.getTopicsList(null, null, offset, limit);
} @Override protected void onRefresh(GetTopicsListEvent event, HeaderFooterAdapter adapter) {
super.onRefresh(event, adapter);
mDataCache.saveTopicsListObj(adapter.getDatas());
} @Override protected void onLoadMore(GetTopicsListEvent event, HeaderFooterAdapter adapter) {
// TODO 排除重复数据
super.onLoadMore(event, adapter);
mDataCache.saveTopicsListObj(adapter.getDatas());
} @Override public void onDestroyView() {
super.onDestroyView();
// 存储 PageIndex
mConfig.saveTopicListPageIndex(pageIndex);
// 存储 RecyclerView 滚动位置
View view = mRecyclerView.getLayoutManager().getChildAt(0);
int lastPosition = mRecyclerView.getLayoutManager().getPosition(view);
mConfig.saveTopicListState(lastPosition, 0);
}
}
3.6.private NewsListFragment mFragment2;
新闻页面碎片。
源代码如下:
/*
* Copyright 2017 GcsSloop
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Last modified 2017-04-09 05:15:40
*
* GitHub: https://github.com/GcsSloop
* Website: http://www.gcssloop.com
* Weibo: http://weibo.com/GcsSloop
*/ package com.gcssloop.diycode.fragment; import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.View; import com.gcssloop.diycode.fragment.base.SimpleRefreshRecyclerFragment;
import com.gcssloop.diycode.fragment.provider.NewsProvider;
import com.gcssloop.diycode_sdk.api.news.bean.New;
import com.gcssloop.diycode_sdk.api.news.event.GetNewsListEvent;
import com.gcssloop.diycode_sdk.log.Logger;
import com.gcssloop.recyclerview.adapter.multitype.HeaderFooterAdapter; import java.util.List; /**
* 首页 news 列表
*/
public class NewsListFragment extends SimpleRefreshRecyclerFragment<New, GetNewsListEvent> { private boolean isFirstLaunch = true; public static NewsListFragment newInstance() {
Bundle args = new Bundle();
NewsListFragment fragment = new NewsListFragment();
fragment.setArguments(args);
return fragment;
} @Override public void initData(HeaderFooterAdapter adapter) {
// 优先从缓存中获取数据,如果是第一次加载则恢复滚动位置,如果没有缓存则从网络加载
List<Object> news = mDataCache.getNewsListObj();
if (null != news && news.size() > 0) {
Logger.e("news : " + news.size());
pageIndex = mConfig.getNewsListPageIndex();
adapter.addDatas(news);
if (isFirstLaunch) {
int lastPosition = mConfig.getNewsListLastPosition();
mRecyclerView.getLayoutManager().scrollToPosition(lastPosition);
isFirstAddFooter = false;
isFirstLaunch = false;
}
} else {
loadMore();
}
} @Override protected void setAdapterRegister(Context context, RecyclerView recyclerView,
HeaderFooterAdapter adapter) {
adapter.register(New.class, new NewsProvider(getContext()));
} @NonNull @Override protected String request(int offset, int limit) {
return mDiycode.getNewsList(null, offset,limit);
} @Override protected void onRefresh(GetNewsListEvent event, HeaderFooterAdapter adapter) {
super.onRefresh(event, adapter);
mDataCache.saveNewsListObj(adapter.getDatas());
} @Override protected void onLoadMore(GetNewsListEvent event, HeaderFooterAdapter adapter) {
// TODO 排除重复数据
super.onLoadMore(event, adapter);
mDataCache.saveNewsListObj(adapter.getDatas());
} @Override public void onDestroyView() {
super.onDestroyView();
// 存储 PageIndex
mConfig.saveNewsListPageIndex(pageIndex);
// 存储 RecyclerView 滚动位置
View view = mRecyclerView.getLayoutManager().getChildAt(0);
int lastPosition = mRecyclerView.getLayoutManager().getPosition(view);
mConfig.saveNewsListPosition(lastPosition);
}
}
3.7.private SitesListFragment mFragment3;
网站页面碎片。
源代码如下:
/*
* Copyright 2017 GcsSloop
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Last modified 2017-04-09 14:32:41
*
* GitHub: https://github.com/GcsSloop
* Website: http://www.gcssloop.com
* Weibo: http://weibo.com/GcsSloop
*/ package com.gcssloop.diycode.fragment; import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView; import com.gcssloop.diycode.fragment.base.RefreshRecyclerFragment;
import com.gcssloop.diycode.fragment.bean.SiteItem;
import com.gcssloop.diycode.fragment.bean.SitesItem;
import com.gcssloop.diycode.fragment.provider.SiteProvider;
import com.gcssloop.diycode.fragment.provider.SitesProvider;
import com.gcssloop.diycode_sdk.api.sites.bean.Sites;
import com.gcssloop.diycode_sdk.api.sites.event.GetSitesEvent;
import com.gcssloop.diycode_sdk.log.Logger;
import com.gcssloop.recyclerview.adapter.multitype.HeaderFooterAdapter; import java.io.Serializable;
import java.util.ArrayList;
import java.util.List; /**
* 首页 sites 列表
*/
public class SitesListFragment extends RefreshRecyclerFragment<Sites, GetSitesEvent> { public static SitesListFragment newInstance() {
Bundle args = new Bundle();
SitesListFragment fragment = new SitesListFragment();
fragment.setArguments(args);
return fragment;
} @Override public void initData(HeaderFooterAdapter adapter) {
setLoadMoreEnable(true);
List<Serializable> sitesList = mDataCache.getSitesItems();
if (sitesList != null) {
Logger.e("sites : " + sitesList.size());
mAdapter.addDatas(sitesList);
setLoadMoreEnable(false);
} else {
loadMore();
}
} @Override
protected void setAdapterRegister(Context context, RecyclerView recyclerView,
HeaderFooterAdapter adapter) {
mAdapter.register(SiteItem.class, new SiteProvider(getContext()));
mAdapter.register(SitesItem.class, new SitesProvider(getContext()));
} @NonNull @Override protected RecyclerView.LayoutManager getRecyclerViewLayoutManager() {
GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 2);
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return (mAdapter.getFullDatas().get(position) instanceof SiteItem) ? 1 : 2;
}
});
return layoutManager;
} @NonNull @Override protected String request(int offset, int limit) {
return mDiycode.getSites();
} @Override protected void onRefresh(GetSitesEvent event, HeaderFooterAdapter adapter) {
toast("刷新成功");
convertData(event.getBean());
} @Override protected void onLoadMore(GetSitesEvent event, HeaderFooterAdapter adapter) {
toast("加载成功");
convertData(event.getBean());
} @Override protected void onError(GetSitesEvent event, String postType) {
toast("获取失败");
} // 转换数据
private void convertData(final List<Sites> sitesList) {
ArrayList<Serializable> items = new ArrayList<>();
for (Sites sites : sitesList) { items.add(new SitesItem(sites.getName())); for (Sites.Site site : sites.getSites()) {
items.add(new SiteItem(site.getName(), site.getUrl(), site.getAvatar_url()));
} if (sites.getSites().size() % 2 == 1) {
items.add(new SiteItem("", "", ""));
}
} mAdapter.clearDatas();
mAdapter.addDatas(items);
mDataCache.saveSitesItems(items);
setLoadMoreEnable(false);
}
}
3.8.private boolean isToolbarFirstClick=true;
如果是第一次
那么提示:双击回到顶部。
4.分析MainActivity中的部分函数
4.1.首先是复写getLayoutId(),获得布局ID。
在BaseActivity中定义的抽象函数。由于继承关系,必须要实现。
4.2.然后是复写initViews(ViewHolder holder,View root)
在BaseActivity中定义的抽象函数。由于继承关系,必须要实现。
initMenu(holder)==>初始化菜单
initViewPager(holder)==>初始化页面
4.3.初始化菜单(包括侧边栏菜单和顶部菜单选项
initMenu(ViewHoler holder)
实图对应关系
4.4.loadMenuData()
加载侧边栏菜单数据(与用户相关)
实图对应关系
4.5.初始化ViewPager
4.5.1.实图对应关系
4.5.2.Viewpager设置适配器
这里通过一个系统类==>FragmentPagerAdapter来设置页面。
这个项目有3个types。Topics,News,Sites。但是额外添加了一列Test,方便测试。
复写了三个方法。
①.public Fragement getItem(int position)
②.public int getCount()
③.public CharSequence getPageTitle(int position)
4.5.3.ViewPager设置监听器
主页面ViewPage设置监听,复写三个方法。
①.public void onPageScrolled(...)
②.public void onPageSelected(int position)
③.public void onPageScrollStateChanged(int state)
4.5.4.设置其他属性
首先从我的配置中获得当前是哪个页面类型。
然后将这个页面类型在ViewPager中设置为当前页。然后可以左右切换了。
和上面的Tab布局挂钩,这样,页面切换上面的Tab也会变化。
注意点:
①如果我注释了mTabLayout.setupWithViewPager(mViewPager)之后
将变成这样:
可以左右滑动,当时上面没有Tab提示了。
②.如果我注释掉mViewPager.setCurrentItem(mCurrentPosition)
那么,我每次退出APP,但没有杀死进程,然后再点进去
都是从第一个Tab开始。
反之,如果我没有注释掉这行代码,那么APP就能记住这个Tab。
那么,下次点进去就能回到这个Tab。
4.6.快速回到顶部
首先确定当前页面是哪个类型。
因为每个页面类型是不一样的,虽然Topic和News页面很相似,但是Sites页面是不同的。
所以每个mFragment中也有自己的qucikToTop()函数。
这里为了方便管理,采用了同一个函数,但是执行的却是不同代码。
4.7.判断用户是否成功登陆,成功登出。
@Subscribe(threadMode=ThreadMode.MAIN)==>表示该函数在主线程UI线程中执行。
用户登陆成功,或者未登陆,左侧的菜单栏是不一样的。
5.继续分析MainActivity中的剩下的函数
5.1.剩下所有的函数预览(全是@Override)
都是一些有关菜单选项,点击事件,活动销毁,自带的返回键的一些复写方法。
5.2.onBackPressed()
意义:左侧菜单栏打开的时候,按返回键,则菜单栏关闭。
5.3.void onCreateOptionsMenu(Menu menu)
意义:创建标题栏的菜单项。
图解:
解释:
第一个item,通知图标。app:showAsAction:always代表在标题栏上出现。
第二个item,设置的第一个,app:showAsAction:never代表点了三个点才出现。
第三个item,设置的第二个,android:orderInCategory代表优先级,谁大谁在前面。
注意点:
如果我把第一个app:showAsAction修改成never,之后会变成这样。
5.4.boolean onOptionsItemSelected(MenuItem item)
处理菜单真实点击事件。
如果点击了通知,则调转到==>NotificationActivity活动页面。
如果点击了设置,则跳转到==>SettingActivity活动页面。
5.5.boolean onNavigationItemSelected(MenuItem item)
因为implements了一个接口:Navigation.OnNavigationItemSelectedListener
所以这里必须要实现这个类。
源代码为:
作用:如果点击了我的帖子,先判断是否登录,然后决定跳转到MyTopicActivity.InfoType.MY_TOPIC。
如果点击了我的收藏,先判断是否登录,然后决定跳转到MyTopicActivity.InfoType.MY_COLLECT。
图解:
5.6.onDestroy()
意义:EventBus反注册。防止内容泄露。
配置退出页面前当前的页面类型。
5.7.onClick(View v)
处理点击事件。
首先是第一次,仅仅是第一次点击toolbar,提示用户有这个功能。没有实质上的返回顶部。
然后是浮动按钮,点击后判断是哪个碎片,执行返回到顶部。
6.简单总结一下
6.1.其实应该早一点分析这个MainActivity,但是如果一下子接触这个东西,可能会有很多不知道的地方,然后
会影响到内心情绪,所以干脆先把一些简单的,能看懂的地方先摸清,然后再尝试从主活动开始。
6.2.首先要理解这个MainActivity是继承了BaseActivity,主要是有两个抽象方法,基本上每个活动都会有的东西,
标题栏,toast提示,打开另一个活动,布局id,视图ViewHolder。
6.3.然后理解实现Navigation.OnNavigationItemSelectedListener接口,就是左侧菜单栏的点击事件,布局效果直接
利用app:headerLayout和app:Menu两者结合创建这个左侧菜单布局效果。
6.4.然后理解实现View.OnClickListener,主活动中,有两处点击事件(不包括菜单),一个是标题栏,第一次单击
标题栏,会提示双击会回到顶部。还有一个浮动按钮,单击回到顶部。
6.5.然后数据定义有数据缓存DataCache,用户配置Config,这些都是一些通用类,当然耦合度也比较低,所以
可以放心用做其他项目。然后是三大巨头,三个Tab碎片,利用Fragment来实现。
6.6.理解主活动的布局是由最外层的DrawerLayout一个带有抽屉,也就是左侧菜单的布局,左侧布局利用一个
NavigationView来实现,然后左侧布局局部用app:headerLayout和app:menu来组合实现通用效果。
6.7.主活动布局中的主体是include中的app_bar_main.xml,最外层是CoordinatorLayout,就是一个拥有一些
滑动效果的布局方式,然后标题栏用一个AppBarLayout总布局+Toolbar+TabLayout的分布局来实现。然后
主体中的主体是一个include,然后是一个浮动按钮FloatingActionButton。
6.8.主体中的主体content_main.xml结构是一个RelativeLayout中有一个ViewPager,实现分页效果。
6.9.一个小小的贴士:如果在content_main.xml中的RelativeLayout中加入一个tools:showIn=
'@layout/app_bar_main',那么在content_main.xml预览中就能看的这个浮动按钮了。
6.10.在主活动的初始化视图initViews中,EventBus注册+DataCache初始化+Config单例创建+初始化主体的标题栏
+侧边栏菜单+加载侧边栏菜单数据(即判断用户是否处于登录状态),然后是初始化ViewPager。整个界面就
搭建好了,之后就是具体实现怎么加载两个菜单,一个顶部,一个侧边栏,怎么初始化ViewPager了。
6.11.具体的initViewPager,是首先从holder中获得id,然后新建3个主体碎片,然后设置适配器。适配器复写
三个必须实现的函数。然后是页面监听变化效果。也有三个必须实现的函数。然后配置当前页和顶部Tab。
6.12.如何加载用户已经等录过后的具体菜单栏数据。这里需要调用API中的函数和处理缓存数据。
6.13.然后利用ActionBarDrawerToggle将布局中的drawerLayout和Toolbar两者关联起来。然后drawer设置监听器,
drawr.setDrawerListener(上面的toggle)即可。然后toggle.syncState(),就行了。完全关联起来了。
6.14.然后就是左侧的菜单栏设置监听器,因为继承关系,这里复写一个onNavigationItemSelected处理逻辑即可。
6.15.然后处理双击标题栏的快速回到顶部的实质代码。采用了一个手势效果,GestureDetector类来定义双击效果。
然后调用自定义的quickToTop()函数来实现。当然这样不行,要将这个手势效果加到toolbar中,所以下面
继续写一个toolbar触摸监听事件,将手势监听和toolbar关联一下,在toolbar触摸是,将手势执行即可。
6.16.侧边栏菜单数据相对来说要负杂一些,首先要判断用户是否登录,然后看能否从缓存中读取用户信息,如果
登录过,先从Cache中获得我的相关数据,否则调用API得到我的信息。然后设置头像监听,跳转到User活动。
6.17.主体标题栏的设置是通过复写的函数onOptionsItemSelected(MenuItem item)来实现。实现一些具体跳转。
6.18.MainActivity相对于其他活动来说的确复杂很多,但是这个活动是深入了解其他活动的大门,开启了这个大门
之后,其他活动就如鱼得水了。而且这个项目是真的棒,我感觉会学到很多新的东西。
Diycode开源项目 MainActivity分析的更多相关文章
- Diycode开源项目 BaseApplication分析+LeakCanary第三方+CrashHandler自定义异常处理
1.BaseApplication整个应用的开始 1.1.看一下代码 /* * Copyright 2017 GcsSloop * * Licensed under the Apache Licens ...
- DiyCode开源项目 BaseActivity 分析
1.首先将这个项目的BaseActivity源码拷贝过来. /* * Copyright 2017 GcsSloop * * Licensed under the Apache License, Ve ...
- Diycode开源项目 UserActivity分析
1.效果预览 1.1.实际界面预览 1.2. 这是MainActivity中的代码 这里执行了跳转到自己的用户界面的功能. 1.3.点击头像或者用户名跳转到别人的页面 UserActivity的结构由 ...
- Diycode开源项目 LoginActivity分析
1.首先看一下效果 1.1.预览一下真实页面 1.2.分析一下: 要求输入Email或者用户名,点击编辑框,弹出键盘,默认先进入输入Email或用户名编辑框. 点击密码后,密码字样网上浮动一段距离,E ...
- Diycode开源项目 ImageActivity分析
1.首先看一下效果 1.1做成了一个GIF 1.2.我用格式工厂有点问题,大小无法调到手机这样的大小,目前还没有解决方案. 1.3.网上有免费的MP4->GIF,参考一下这个网站吧. 1.4.讲 ...
- Diycode开源项目 TopicContentActivity分析
1.效果预览以及布局分析 1.1.实际效果预览 左侧话题列表的布局是通过TopicProvider来实现的,所以当初分析话题列表就没有看到布局. 这里的话题内容不是一个ListView,故要自己布局. ...
- DiyCode开源项目 AboutActivity分析
1.首先看一下效果 这是手机上显示的效果: 1.1首先是一个标题栏,左侧一个左箭头,然后一个图标. 1.2然后下方是一个可以滑动的页面. 1.3分成了7个部分. 1.4DiyCode的图标. 1.5然 ...
- DiyCode开源项目 TopicActivity 分析
1.首先看看TopActivity效果. 2.TopicActivity是一个继承BaseActivity的.前面分析过BaseActivity了.主要有一个标题栏,有返回的图标. 3.贴一下T ...
- Diycode开源项目 SitesListFragment分析
1.效果预览 1.1.网站列表实际界面 1.2.注意这个界面没有继承SimpleRefreshRecycleFragment 前面的话题和新闻继承了SimpleRefreshRecyclerFragm ...
随机推荐
- NopI 导出数据
protected void exportAward(DataSet dsResult) { if (dsResult != null) { string fileName = System.Web. ...
- [Freemarker] 在Java中简单实现对Freemarker的引用
Demo目录结构 ├─src │ ├─main │ │ ├─java │ │ │ └─demo │ │ │ └─freemarker │ │ │ ├─main │ │ │ │ Test.java │ ...
- Docker的安全问题以及一些预防方案
http://blog.csdn.net/Ruidu_Doer/article/details/53401523
- 移植mavlink协议到STM32详细教程
1准备材料, 首先准备一个带串口的stm32程序(这里选用整点原子的官方串口例程这里自己去找不讲)作者:恒久力行 QQ:624668529,然后去mavlink官网下载mavlink源码,这里重点讲解 ...
- 【Linux/Ubuntu学习 10】unbuntu 下 eclipse 中文乱码的解决
wangdd@wdd-pc:~$ gedit /var/lib/locales/supported.d/local 添加: zh_CN.GBK GBK zh_CN.GB2312 GB2312 终端执行 ...
- 优化Linux的内核参数来提高服务器并发处理能力
提高Linux系统下的负载能力,可以使用nginx等原生并发处理能力就很强的web服务器 使用Apache的可以启用其Worker模式,来提高其并发处理能力 修改Linux的内核相关TCP参数,来最大 ...
- LeetCode Remove Element删除元素
class Solution { public: int removeElement(int A[], int n, int elem) { ]; int i,num=n; ;i<n;i++){ ...
- UOJ#126【NOI2013】快餐店
[NOI2013]快餐店 链接:http://uoj.ac/problem/126 YY了一个线段树+类旋转卡壳的算法.骗了55分.还比不上$O(n^2)$暴力T^T 题目实际上是要找一条链的两个端点 ...
- SAP Cloud for Customer Extensibility的设计与实现
今天的文章来自Jerry的同事,SAP成都研究院C4C开发团队的开发人员徐欢(Xu Boris).徐欢就坐我左手边的位置,因此我工作中但凡遇到C4C的技术问题,一扭头就可以请教他了,非常方便.下图是他 ...
- System.FormatException: GUID 应包含带 4 个短划线的 32 位数(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)。解决办法
查一下数据库的UID数据是否格式正确,如: 错误格式1: {E056BB36-D824-4106-A9C3-D8D8B9ADC1C 错误格式2: E056BB36-D824-4106-A9C3-D8D ...