Android Gank.io客戶端

一直想寫一個(gè)類似于新聞客戶端的APP,但是沒(méi)有碰到免費(fèi)的API。不知道什么時(shí)候在網(wǎng)上兜兜裝轉(zhuǎn)看到了gank.io有開(kāi)放的API,立馬著手準(zhǔn)備寫一個(gè)客戶端。

一、客戶端設(shè)計(jì)

主要框架:MVC、RxJava、Retrifit
使用MVC主要是因?yàn)檎wAPP體量較小,沒(méi)有較多的業(yè)務(wù)邏輯,沒(méi)有明顯的模塊化。網(wǎng)絡(luò)請(qǐng)求選擇Retrifit與RxJava相結(jié)合的寫法,之前做項(xiàng)目一直都在這樣用,不過(guò)RxJava沒(méi)有使用2.0以后的版本。

二、產(chǎn)品設(shè)計(jì)

整理布局使用的類似DrawerLayout的SlidingRootNav,左側(cè)為RecyclerView列表,顯示所有分類,這里SlidingRootNav我稍微改了一下,用戶選擇分類的時(shí)候就把左側(cè)隱藏掉,回到Activity的頁(yè)面。數(shù)據(jù)列表是在Fragment中,根據(jù)用戶選擇的分類不同在Activity中加載不同F(xiàn)ragment。列表這里使用的都是RecyclerView,福利部分為瀑布流的形式展示。用戶點(diǎn)擊item跳轉(zhuǎn)的WebViewActivity,可以復(fù)制鏈接,分享(第一版沒(méi)有添加功能),收藏。在福利分類中,點(diǎn)擊item會(huì)到一個(gè)圖片預(yù)覽的Fragment中,可以保存圖片到相冊(cè)中。

三、關(guān)鍵部分代碼

***********************************網(wǎng)絡(luò)請(qǐng)求處理************************************
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());
    }
}
***********************************發(fā)送網(wǎng)絡(luò)請(qǐng)求************************************
public interface InterfaceService {

    @GET("{type}/{pageSize}/{page}")
    Observable<ResponseInfo> getAllInfo(@Path("type") String type,@Path("pageSize")String pageSize, @Path("page")String page);
}
**************************************首頁(yè)***************************************
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("關(guān)閉");
        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********************************
//主要處理緩存,兩次啟動(dòng)間隔12小時(shí),會(huì)把除了“我的收藏”模塊的其他緩存數(shù)據(jù)清除,這里也不包括圖片緩存。

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();
    }
}

四、使用到的第三方類庫(kù)

    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'

五、截圖

首頁(yè)

首頁(yè)_2.jpg

福利.jpg

ios.jpg

六、項(xiàng)目地址

https://github.com/CFuLiang/Gank

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容