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;
        }
    }
}
View Code

 

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>
View Code

 

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。

  

  作用:实现浮动按钮的滚动效果。  

  参考文章:CoordinatorLayout与滚动的处理。

 

2.4.然后是AppBarLayout。

  作用就是一些动画效果吧。上滑隐藏了标题栏。

  就像下图这样:

  

  参考文章:AppBarLayout子View的动作。

 

2.5.然后是TabLayout。

  作用:就是添加一个导航栏,切换后ViewPager也随之切换。

  类似于这样的效果:

  

  参考文章:Design库-TabLayout属性详解。

 

2.6.然后是浮动按钮==>FloatingActionButton。

  这种东西:

    

 

  参考文章: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_");
    }
}
View Code

  

  3.2.2.看一下DataCache中定义的变量。

    

    这里定义的M是一兆缓存单位的意思。

    Acache是定义在sdk中处理缓存的类。

    LruCache<String,Object>是Android提供的缓存工具类。

    参考一下这篇文章==>详细解读LruCache类。

 

   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);
    }
}
View Code

  

  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);
    }
}
View Code

 

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);
    }
}
View Code

 

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);
    }
}
View Code

 

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相对于其他活动来说的确复杂很多,但是这个活动是深入了解其他活动的大门,开启了这个大门

  之后,其他活动就如鱼得水了。而且这个项目是真的棒,我感觉会学到很多新的东西。

 

 

posted @ 2017-11-18 14:43  Jason_Jan  阅读(675)  评论(0编辑  收藏  举报