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

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

    既然选择了,便不顾风雨兼程。Just follow yourself.
  • 相关阅读:
    5.1重磅活动:区块链免费送书
    Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
    Linux负载均衡利器(LVS)
    豌豆荚Redis集群方案:Codis
    Spring Boot Redis Cluster实战
    高性能代理缓存服务器—Squid
    Facebook分布式框架—Thrift介绍。
    Java 高级面试知识点汇总!
    (4)设计模式-建造者模式
    (3)设计模式-单例模式
  • 原文地址:https://www.cnblogs.com/Jason-Jan/p/7856671.html
Copyright © 2011-2022 走看看