Android Gank.io客户端

一直想写一个类似于新闻客户端的APP,但是没有碰到免费的API。不知道什么时候在网上兜兜装转看到了gank.io有开放的API,立马着手准备写一个客户端。

一、客户端设计

主要框架:MVC、RxJavaRetrifit
使用MVC主要是因为整体APP体量较小,没有较多的业务逻辑,没有明显的模块化。网络请求选择Retrifit与RxJava相结合的写法,之前做项目一直都在这样用,不过RxJava没有使用2.0以后的版本。

二、产品设计

整理布局使用的类似DrawerLayout的SlidingRootNav,左侧为RecyclerView列表,显示所有分类,这里SlidingRootNav我稍微改了一下,用户选择分类的时候就把左侧隐藏掉,回到Activity的页面。数据列表是在Fragment中,根据用户选择的分类不同在Activity中加载不同Fragment。列表这里使用的都是RecyclerView,福利部分为瀑布流的形式展示。用户点击item跳转的WebViewActivity,可以复制链接,分享(第一版没有添加功能),收藏。在福利分类中,点击item会到一个图片预览的Fragment中,可以保存图片到相册中。

三、关键部分代码

***********************************网络请求处理************************************
public class InterfaceAPI {

    private InterfaceService interfaceService;

    public InterfaceAPI() {
        interfaceService = new RetrofitClient().getInterfaceService();
    }


    public Observable<ResponseInfo> getAllData(String type,String pageSize ,String page) {
        return interfaceService.getAllInfo(type,pageSize,page).onErrorResumeNext(new Func1<Throwable, Observable<? extends ResponseInfo>>() {
            @Override
            public Observable<? extends ResponseInfo> call(Throwable throwable) {
                return Observable.error(RxHttpHelper.convertIOEError(throwable));
            }
        }).flatMap(new Func1<ResponseInfo, Observable<ResponseInfo>>() {
            @Override
            public Observable<ResponseInfo> call(ResponseInfo responseInfo) {
                if (responseInfo == null) {
                    return Observable.error(new RequestErrorThrowable(HttpErrorInfo.CODE_OF_PARSE_REQUEST_FAILURE,
                            HttpErrorInfo.MSG_OF_PARSE_REQUEST_FAILURE));
                }else {
                    if (responseInfo.isError){
                        return Observable.error(new RequestErrorThrowable("-1", "获取失败"));
                    }else {
                        return Observable.just(responseInfo);
                    }
                }
            }
        }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
    }
}
***********************************发送网络请求************************************
public interface InterfaceService {

    @GET("{type}/{pageSize}/{page}")
    Observable<ResponseInfo> getAllInfo(@Path("type") String type,@Path("pageSize")String pageSize, @Path("page")String page);
}
**************************************首页***************************************
public class SampleActivity extends AppCompatActivity{

    private String[] screenTitles;

    private SlidingRootNavBuilder builder;
    private WelfareFragment welfareFragment;
    private AllListFragment allListFragment;
    private CollectFragment collectFragment;
    private MenuAdapter adapter;
    private long exitTime = 0;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        toolbar.setTitleTextColor(ContextCompat.getColor(this,R.color.white));
        setSupportActionBar(toolbar);
        builder = new SlidingRootNavBuilder(this);
        builder .withToolbarMenuToggle(toolbar)
                .withMenuOpened(false)
                .withSavedState(savedInstanceState)
                .withMenuLayout(R.layout.menu_left_drawer)
                .inject();
        screenTitles = loadTitleString();

        adapter = new MenuAdapter(this);
        adapter.setOnItemClickListener(new MenuAdapter.ItemClickListener() {
            @Override
            public void onItemClick(int position) {
                adapter.setSelected(position);
                show(position);
            }
        });
        RecyclerView list = (RecyclerView) findViewById(R.id.list);
        list.setNestedScrollingEnabled(false);
        list.setLayoutManager(new LinearLayoutManager(this));
        list.setAdapter(adapter);
        show(0);
        LinearLayout about = (LinearLayout)findViewById(R.id.about_layout);
        about.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showDialog();
            }
        });
    }

    @Override
    public void onBackPressed() {
        if ((System.currentTimeMillis() - exitTime) > 2000) {
            Snackbar.make(getWindow().getDecorView(),"再按一次退出", BaseTransientBottomBar.LENGTH_SHORT).show();
            exitTime = System.currentTimeMillis();
        } else {
            quit();
        }
    }

    protected void quit() {
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        startActivity(intent);
        moveTaskToBack(true);
        finish();
    }

    private void showDialog(){
        MaterialDialog.Builder builder = new MaterialDialog.Builder(this);
        builder.title("About");
        builder.negativeText("关闭");
        builder.negativeColorRes(R.color.colorAccent);
        builder.content(R.string.about);
        builder.onNegative(new MaterialDialog.SingleButtonCallback() {
            @Override
            public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
                dialog.dismiss();
            }
        });
        builder.build().show();
    }

    public void show(int position){
        adapter.setSelected(position);
        Fragment selectedScreen;
        if (position == 5){
            if (welfareFragment == null){
                welfareFragment = new WelfareFragment();
            }
            selectedScreen = welfareFragment;
        }else if (position == 7){
            if (collectFragment == null){
                collectFragment = new CollectFragment();
            }
            selectedScreen = collectFragment;
        }
        else {
            if (allListFragment == null) {
                allListFragment = new AllListFragment();
            }
            selectedScreen = allListFragment;
            if (allListFragment.isAdded()){
                allListFragment.setType(screenTitles[position]);
            }else {
                Bundle bundle = new Bundle();
                bundle.putString("type",screenTitles[position]);
                allListFragment.setArguments(bundle);
            }
        }
        if (getSupportActionBar() != null){
            if (position == 0){
                getSupportActionBar().setTitle(getString(R.string.app_name));
            }else {
                getSupportActionBar().setTitle(screenTitles[position]);
            }
        }
        builder.hideMenu();
        showFragment(selectedScreen);
    }

    private void showFragment(Fragment fragment) {
        getFragmentManager().beginTransaction()
                .replace(R.id.container, fragment)
                .commit();
    }

    private String[] loadTitleString() {
        return getResources().getStringArray(R.array.title_list);
    }

}
**********************************自定义application********************************
//主要处理缓存,两次启动间隔12小时,会把除了“我的收藏”模块的其他缓存数据清除,这里也不包括图片缓存。

public class MyApplication extends Application{

    private String [] title;

    @Override
    public void onCreate() {
        super.onCreate();
        DataCache.instance.init(MyApplication.this);
        Long saveTime = DataCache.instance.getCacheData("fuliang","limitTime");
        Long nowTime = System.currentTimeMillis();
        if (saveTime == null){
            DataCache.instance.saveCacheData("fuliang","limitTime",nowTime);
        }else {
            title = getResources().getStringArray(R.array.ld_activityScreenTitles);
            int i = 0;
            if (nowTime - saveTime > 12*60*60*1000){
                while (i<title.length-1){
                    DataCache.instance.clearCacheData("fuliang",title[i]);
                    i++;
                }
            }
        }
    }
}
************************************列表适配器************************************
/**
 * Created by lfu on 2017/6/8.
 */

public class AllListFragment extends Fragment implements WaveSwipeRefreshLayout.OnRefreshListener{

    private WaveSwipeRefreshLayout swipeLayout;
    private RecyclerView recyclerView;
    private AllDataAdapter allDataAdapter;
    private ArrayList<ResultsList> allDataList;
    private String itemType;
    private LoadingFragment loadingFragment;
    private Animator spruceAnimator;
    private int page = 1;
    private boolean isLoadMore = false;
    private boolean isHaveMore = true;

    @Nullable
    @Override
    public View onCreateView(final LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.all_fragment_layout,container,false);
        recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
        LinearLayoutManager layoutManager = new MyLayoutManager(getActivity()){
            @Override
            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
                super.onLayoutChildren(recycler, state);
                spruceAnimator = new Spruce.SpruceBuilder(recyclerView)
                        .sortWith(new DefaultSort(100))
                        .animateWith(DefaultAnimations.shrinkAnimator(recyclerView, 800),
                                ObjectAnimator.ofFloat(recyclerView, "translationX", -recyclerView.getWidth(), 0f).setDuration(800))
                        .start();
            }
        };
        recyclerView.setLayoutManager(layoutManager);
        swipeLayout = (WaveSwipeRefreshLayout)view.findViewById(R.id.swipe_layout);
        swipeLayout.setColorSchemeColors(ContextCompat.getColor(getActivity(),R.color.white),ContextCompat.getColor(getActivity(),R.color.white));
        swipeLayout.setOnRefreshListener(this);
        swipeLayout.setWaveColor(ContextCompat.getColor(getActivity(),R.color.blue));
        allDataAdapter = new AllDataAdapter(getActivity());
        allDataAdapter.setReloadListener(new AllDataAdapter.ReloadListener() {
            @Override
            public void onReload() {
                page++;
                isLoadMore = true;
                getDataFromInternet();
                allDataAdapter.startReload();
            }
        });
        allDataAdapter.setOnItemClickListener(new AllDataAdapter.ItemClickListener() {
            @Override
            public void onItemClick(ResultsList model) {
                Intent intent = new Intent(getActivity(), WebActivity.class);
                intent.putExtra("model",model);
                startActivity(intent);
            }
        });
        recyclerView.addOnScrollListener(new EndLessOnScrollListener(layoutManager) {
            @Override
            public void onLoadMore() {
                if (isHaveMore){
                    page++;
                    isLoadMore = true;
                    getDataFromInternet();
                }
            }
        });
        itemType = getArguments().getString("type");
        setType(itemType);
        return view;
    }


    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }

    private void getDataFromInternet(){
        BusinessHelper.getAllData(itemType,"30",String.valueOf(page)).subscribe(new Action1<ResponseInfo>() {
            @Override
            public void call(ResponseInfo responseInfo) {
                if (swipeLayout.isRefreshing()) {
                    swipeLayout.setRefreshing(false);
                }
                if (loadingFragment != null){
                    loadingFragment.removeSelf(getFragmentManager());
                }
                if (responseInfo.results.size() < 30){
                    isHaveMore = false;
                }

                if (page == 1){
                    DataCache.instance.saveCacheData("fuliang", TypeHelper.getType(itemType),responseInfo.results);
                }
                allDataList = responseInfo.results;
                setViewData(isLoadMore);

                if (spruceAnimator != null){
                    spruceAnimator.start();
                }

            }
        }, new Action1<Throwable>() {
            @Override
            public void call(Throwable throwable) {
                if (loadingFragment != null){
                    loadingFragment.loadFail();
                }
                if (isLoadMore){
                    page--;
                    isHaveMore = true;
                    allDataAdapter.loadMoreFail();
                }
            }
        });
    }

    private void setViewData(boolean isLoadMore){
        if (isLoadMore){
            allDataAdapter.addData(allDataList);
        }else {
            allDataAdapter.setData(allDataList);
        }
        if (recyclerView.getAdapter() == null){
            recyclerView.setAdapter(allDataAdapter);
        }else {
            if (!isLoadMore){
                recyclerView.scrollTo(0,0);
            }
            allDataAdapter.notifyDataSetChanged();
        }

    }

    public void setType(String type){
        itemType = type;
        isLoadMore = false;
        page = 1;
        if (recyclerView != null){
            recyclerView.removeAllViews();
            if (allDataList != null){
                allDataList.clear();
                allDataAdapter.notifyDataSetChanged();
            }
        }
        if (loadingFragment != null){
            loadingFragment.removeSelf(getFragmentManager());
        }
        allDataList = DataCache.instance.getCacheData("fuliang",TypeHelper.getType(type));
        if (allDataList == null){
            if (!type.equals("我的收藏")){
                showFragment();
                getDataFromInternet();
            }else {
                showFragment();
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        loadingFragment.noData();
                    }
                },500);
            }
            return;
        }
        if (allDataList.size()>0){
            setViewData(isLoadMore);
            if (spruceAnimator != null){
                spruceAnimator.start();
            }
        }else {
            showFragment();
            getDataFromInternet();
        }
    }

    private void showFragment() {
        loadingFragment= new LoadingFragment();
        loadingFragment.setReloadListener(new LoadingFragment.ReloadData() {
            @Override
            public void reloadData() {
                loadingFragment.reloadData();
                page = 1;
                getDataFromInternet();
            }
        });
        getFragmentManager().beginTransaction()
                .replace(R.id.loading_layout, loadingFragment)
                .commitAllowingStateLoss();
    }

    @Override
    public void onRefresh() {
        page = 1;
        getDataFromInternet();
    }
}

四、使用到的第三方类库

    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support:design:25.3.1'
    compile 'com.android.support:recyclerview-v7:25.3.1'
    compile 'io.reactivex:rxandroid:1.1.0'
    compile 'io.reactivex:rxjava:1.1.0'
    compile 'com.squareup.okhttp3:okhttp:3.4.1'
    compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
    compile 'com.squareup.retrofit2:converter-gson:2.0.0'
    compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0'
    compile 'com.android.support:cardview-v7:25.3.1'
    compile 'com.github.recruit-lifestyle:WaveSwipeRefreshLayout:1.6'
    compile 'com.afollestad.material-dialogs:core:0.9.4.5'
    compile 'com.afollestad.material-dialogs:commons:0.9.4.5'
    compile 'com.squareup.picasso:picasso:2.5.2'
    compile 'com.android.support:support-v4:25.3.1'
    compile 'com.willowtreeapps.spruce:spruce-android:1.0.1'
    compile 'com.oguzdev:CircularFloatingActionMenu:1.0.2'

五、截图

首页

首页_2.jpg

福利.jpg

ios.jpg

六、项目地址

https://github.com/CFuLiang/Gank

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容