標籤:parse http and view code 回調 manager abs divide
前言
從零開始,手把手帶你實現一個「專註睡前的 APP」。睡覺之前如果能有一個 APP,能讓我們寫一寫這一天的見聞或者心得,同時又能看一會段子、瞄一會好看的妹子,放鬆一下疲憊的身心那該多好,這也是我完成這個 APP 的原因。APP 的全部代碼我已經分享到?Github?上了,需要的直接 點擊這裡,如果喜歡的話,麻煩給個 star,謝謝啦。
本文為這一系列文章的總述,如果覺得篇幅過長,請點擊下面的串連
手把手教你從零開始做一個好看的 APP - Day one
手把手教你從零開始做一個好看的 APP - Day two
手把手教你從零開始做一個好看的 APP - Day three
手把手教你從零開始做一個好看的 APP - Day four
手把手教你從零開始做一個好看的 APP - Day five
在開始寫本文之前,先來一波效果的展示,看看五天過後我們能實現怎樣的效果
本次的教程分為 5 天,內容分別為:
Day one,準備
Day two,UI 及公用類的封裝
Day three,日記模組
Day four,妹子模組
- Day five,段子模組
Day one
俗話說,萬事開頭難,在開始敲代碼之前,先讓我們來做一些必要的準備,這樣才能事半功倍嘛!
一、功能需求
既然要做一個 APP,那我們首先還是得把 APP 的功能都列出來,有了方向才能更好的努力,因為我想做的是一個專門給睡覺前用的 APP,所以我覺得應該有以下的這些功能
- 1、日記的增刪改
- 2、顯示一些有趣好玩的段子
- 3、瀑布流展示漂亮的妹子
- 4、儲存日記的內容以及緩衝妹子圖片
雖然說需求不多,但是卻要運用到網路、資料存放區、圖片緩衝、UI 設計等內容,相信整個 APP 完成下來,必定能鞏固我們的 Android 基礎。
二、可行性分析
我們這個 APP 主要有三個模組,日記模組主要是運用到了資料庫的知識,難度不大。但是,段子模組和妹子模組的資料要從哪來,這便是要好好考慮的了。幸好現在是個開源的時代,很多的資料,網上已經開源出來了。
我們先來看一下資料的內容
group:?{ text:?"教授在河邊,常常看到兩隻龜,縮著一動不動。有天忍不住好奇,問一農 民:這兩隻烏龜在幹嗎?農民說:他們在pk。教授不解地問:動都沒動過p什麼 k。老農說:他們在比誰壽命長。教授說:可是殼上有甲骨文的那隻,早就死了埃 這時,另一隻猛然探出頭來罵到:md,死了也不吭一聲!有甲骨文的那隻也伸 出頭來:“專家說啥你信啥1", user:?{ user_id:?4669064575, name:?"饅頭啊", avatar_url:?"http://p3.pstatp.com/medium/6237/7969345239",}, content:?"教授在河邊,常常看到兩隻龜,縮著一動不動。有天忍不住好奇,問 一農民:這兩隻烏龜在幹嗎?農民說:他們在pk。教授不解地問:動都沒動過 p什麼k。老農說:他們在比誰壽命長。教授說:可是殼上有甲骨文的那隻,早 就死了埃這時,另一隻猛然探出頭來罵到:md,死了也不吭一聲!有甲骨文 的那隻也伸出頭來:“專家說啥你信啥1",... }
{ id:?"56cc6d1d421aa95caa7076df", type:?"福利", url:?"http://ww1.sinaimg.cn/large/7a8aed7bgw1esxxi1vbq0j20qo0hstcu.jpg", used:?true, who:?"張涵宇"}
上面那兩段代碼分別是段子和妹子模組的 json 類型的資料,我已經將一些沒用的欄位去掉了。剩下的都是我們想要的資料。可以看到段子資料中,有著段子的內容,以及發行者的頭像和名字。而妹子資料中有著圖片的 url、id、以及圖片的類型。相信有了這麼豐富的資料,我們想要完成這個 APP 也是有底氣了。
Day two一、介面的設計及實現
既然我們想要完成一個好看的 APP,那麼好看的介面便是必不可少的,這裡我強烈推薦 APP 介面的設計必須盡量遵從 Google 提出的 Material Design,在這個推薦一個能夠讓我們實現 Material Design 變得更加簡單的網站 material design palette,我這個 APP 的配色就是用這個網站完成的,貼幾張圖片,讓你感受一下它的強大
藉助這個網站便能讓我們完成 APP 的配色以及表徵圖的收集,為下一步功能的實現,先打好了基礎,至於介面的設計就仁者見仁智者見智了,篇幅有限,我就不多講了。
APP 的最終設計效果如下:
二、公用類的實現
因為這個項目有三個模組,有一些東西其實是可以通用的,如果我們先把這些能夠通用的東西,封裝起來,供給所有的模組調用的話,相信會大大提高我們的開發效率。
1、網路工具類的封裝
這個 APP 中,很多地方都要用到網路請求,因此也就很有必要將網路請求封裝起來,因為這個 APP 的規模比較小,因此我選擇了 Volley 這個網路架構作為我們網路請求庫,把網路請求封裝起來,哪個地方需要,調用一下就行了。對於網路請求,我覺得每個程式員都該懂點 HTTP,這裡附上一篇有關 HTTP 的文章 程式員都該懂點 HTTP。
先讓我們來寫個將網路請求進行回調的介面
public interface VolleyResponseCallback { void onSuccess(String response); void onError(VolleyError error);}
然後將網路請求封裝起來
public class VolleyHelper { /** * 用於發送 Get 請求的封裝方法 * * @param context Activity 的執行個體 * @param url 請求的地址 * @param callback 用於網路回調的介面 */ public static void sendHttpGet(Context context, String url, final VolleyResponseCallback callback){ RequestQueue requestQueue = Volley.newRequestQueue(context); StringRequest stringRequest = new StringRequest(url , new Response.Listener<String>() { @Override public void onResponse(String s) { callback.onSuccess(s); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { callback.onError(error); } }); requestQueue.add(stringRequest); }}
2、Json 解析的協助類
因為我們這個 APP 中,擷取到的資料都是 Json 格式的,因此也就有必要將有關的 Json 解析封裝成一個工具類,傳入一個 String 類型的資料,直接得到資料實體類的 List。
public class CommonParser { /** * 用來解析列表性的JSON資料 * 如: * {"success":true,"fileList":[{"filename":"檔案名稱1","fileSize":"檔案大小1"}, * {"filename":"檔案名稱2","fileSize":"檔案大小2"}]} * * @param result 網路返回來的JSON資料 比如:上面的整串資料 * @param successKey 判斷網路是否成功的欄位 比如:上面的success欄位 * @param arrKey 列表的欄位 比如:上面的fileList欄位 * @param clazz 需要解析成的Bean類型 * @param <T> 需要解析成的Bean類型 * @return */ public static <T> List<T> parseForList(String result, String successKey, String arrKey, Class<T> clazz) { List<T> list = new ArrayList<>(); JSONObject rootJsonObject = null; try { rootJsonObject = new JSONObject(result); if (rootJsonObject.getBoolean(successKey)) { JSONArray rootJsonArray = rootJsonObject.getJSONArray(arrKey); Gson g = new Gson(); for (int i = 0; i < rootJsonArray.length(); i++) { T t = g.fromJson(rootJsonArray.getJSONObject(i).toString(), clazz); list.add(t); } } } catch (JSONException e) { e.printStackTrace(); } return list; }}
3、HomeActivity(首頁面)的封裝
首頁面我用的是 TabLayout + ViewPager + Fragment,也是現在主流 APP 首頁面的顯示方式。主介面底部是我們三個模組的表徵圖和名稱,通過左右滑動能實現介面的跳轉。
底部表徵圖的實體類 CommonTabBean
public class CommonTabBean implements CustomTabEntity{ private int selectedIcon; private int unselectedIcon; private String title; public CommonTabBean(String title){ this.title = title; } public CommonTabBean(String title, int selectedIcon, int unselectedIcon) { this.title = title; this.selectedIcon = selectedIcon; this.unselectedIcon = unselectedIcon; } @Override public String getTabTitle() { return title; } @Override public int getTabSelectedIcon() { return selectedIcon; } @Override public int getTabUnselectedIcon() { return unselectedIcon; }}
ViewPager + Fragment 通用的 Adapter
public class CommonPagerAdapter extends FragmentPagerAdapter { private List<Fragment> mFragments; public CommonPagerAdapter(FragmentManager fragmentManager, List<Fragment> mFragments){ super(fragmentManager); this.mFragments = mFragments; } @Override public Fragment getItem(int position) { return mFragments.get(position); } @Override public int getCount() { return mFragments.size(); }}
Day three
關於日記模組的實現,其實我是複用了以前寫過的一個日記 APP,具體的思路和做法,可以參考我的這篇文章 Android 一款十分簡潔、優雅的日記 APP
Day four一、圖片的擷取1、根據返回的資料來編寫圖片的實體類
public class MeiziBean { @SerializedName("_id") private String id; @SerializedName("url") private String imageUrl; @SerializedName("who") private String who; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getImageUrl() { return imageUrl; } public MeiziBean(String imageUrl){ this.imageUrl = imageUrl; }}
2、圖片的展示
可以看到我是用瀑布流的方式來實現圖片的展示,效果還不錯,但其實實現起來也是很簡單的
先寫個圖片的布局作為 RecyclerView 的 Item
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/item_iv_meizi" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" /></android.support.v7.widget.CardView>
可以看到我在 ImageView 的外面加了一個 CardView,這個一種卡片式布局,能讓圖片看起來就像一張卡片一樣,相當的優雅、美觀。
接著編寫 Adapter,將資料和介面進行綁定
public class MeiziAdapter extends RecyclerView.Adapter<MeiziAdapter.MeiziViewHolder> { private List<MeiziBean> mMeiziBeanList; private Fragment mFragment; public MeiziAdapter(List<MeiziBean> mMeiziBeanList, Fragment mFragment){ this.mMeiziBeanList = mMeiziBeanList; this.mFragment = mFragment; } @Override public MeiziViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_meizi, null); return new MeiziViewHolder(view); } @Override public void onBindViewHolder(MeiziViewHolder holder, final int position) { Glide.with(mFragment) .load(mMeiziBeanList.get(position).getImageUrl()) .fitCenter() .dontAnimate() .diskCacheStrategy(DiskCacheStrategy.ALL) .into(holder.mIvMeizi); holder.mIvMeizi.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ArrayList<String> resultList = new ArrayList<String>(); for (MeiziBean meiziBean : mMeiziBeanList) { resultList.add(meiziBean.getImageUrl()); } DetailActivity.startActivity(mFragment.getActivity(), resultList, position); } }); } @Override public int getItemCount() { if(mMeiziBeanList.size() > 0){ return mMeiziBeanList.size(); } return 0; } public static class MeiziViewHolder extends RecyclerView.ViewHolder{ ImageView mIvMeizi; public MeiziViewHolder(View itemView) { super(itemView); mIvMeizi = (ImageView) itemView.findViewById(R.id.item_iv_meizi); } }}
最後在 Fragment 進行資料的擷取,以及布局的初始化就行了
public class MeiziFragment extends Fragment { ...... @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_meizi, container, false); ButterKnife.bind(this, view); initView(); refreshMeizi(); return view; } /** * 重新整理當前介面 */ private void refreshMeizi() { mRefresh.setColorSchemeResources(R.color.colorPrimary); mRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { initView(); mRefresh.setRefreshing(false); } }); } private void initView() { VolleyHelper.sendHttpGet(getActivity(), MeiziApi.getMeiziApi(), new VolleyResponseCallback() { @Override public void onSuccess(String s) { response = s; meiziBeanList = GsonHelper.getMeiziBean(response); mRvShowMeizi.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)); Collections.shuffle(meiziBeanList); mRvShowMeizi.setAdapter(new MeiziAdapter(meiziBeanList, MeiziFragment.this)); } @Override public void onError(VolleyError error) { Logger.d(error); } }); }
3、詳情頁面的展示
乾巴巴的,整個模組只能顯示妹子的圖片怎麼行呢!!!怎麼著也得能查看大圖,根據手勢放大縮小,以及瀏覽下一張圖片才行嘛,說幹就幹。
因為圖片需要有根據手勢來放大縮小的功能,因此我便想到了 PhotoView,這是網上一個大神寫的,繼承自 ImageView 的一個自訂控制項。圖片載入我用的是
Glide,如果沒瞭解過這個庫的,強烈推薦,一行代碼就能搞定圖片載入,你確定不研究一下。這裡附上一篇有關 Glide 的文章 Glide 一個強大的圖片載入架構
public class DetailFragment extends Fragment { public static DetailFragment newInstance(String imageUrl) { DetailFragment fragment = new DetailFragment(); Bundle bundle = new Bundle(); bundle.putString(IMAGE_URL, imageUrl); fragment.setArguments(bundle); return fragment; } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_detail, container, false); ButterKnife.bind(this, view); Bundle bundle = getArguments(); String imageUrl = bundle.getString(IMAGE_URL); Glide.with(this).load(imageUrl).into(mPvShowPhoto); mPvShowPhoto.setOnPhotoTapListener(new PhotoViewAttacher.OnPhotoTapListener() { @Override public void onPhotoTap(View view, float v, float v1) { getActivity().finish(); } @Override public void onOutsidePhotoTap() { } }); return view; }}
Day five一、段子資料的擷取
段子資料的擷取其實跟妹子模組的方法基本一樣
先編寫實體類
public class DuanziBean { @SerializedName("group") private GroupBean groupBean; private String type; public GroupBean getGroupBean() { return groupBean; } public void setGroupBean(GroupBean groupBean) { this.groupBean = groupBean; } public String getType() { return type; } public void setType(String type) { this.type = type; }}
public class GroupBean { private String text; private long id; private UserBean user; public String getText() { return text; } public long getId() { return id; } public UserBean getUser() { return user; } public static class UserBean { private long user_id; private String name; private String avatar_url; public String getName() { return name; } public String getAvatar_url() { return avatar_url; } }}
寫好實體類之後,使用我們之前已經封裝好的網路請求工具以及解析工具,便能將返回的資料,解析成一個包含段子實體類的 List。
二、段子的顯示
老規矩,先寫個 RecyclerView 的 Item
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="40dp" android:paddingLeft="8dp" > <de.hdodenhof.circleimageview.CircleImageView android:id="@+id/duanzi_civ_avatar" android:layout_width="24dp" android:layout_height="24dp" android:src="@drawable/avatar" android:layout_gravity="center" /> <TextView android:id="@+id/duanzi_tv_author" android:paddingLeft="8dp" android:paddingStart="8dp" android:layout_width="match_parent" android:layout_height="16dp" android:text="DeveloperHaoz" android:layout_gravity="center_vertical" /> </LinearLayout> <TextView android:id="@+id/duanzi_tv_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="10dp" android:paddingLeft="40dp" android:paddingRight="10dp" android:text="" /> <include layout="@layout/layout_app_divide"/></LinearLayout>
然後編寫將資料和介面進行綁定的 Adapter
public class DuanziAdapter extends RecyclerView.Adapter<DuanziAdapter.DuanziViewHolder>{ private Fragment mFragment; private List<DuanziBean> mDuanziBeanList; public DuanziAdapter(Fragment fragment, List<DuanziBean> duanziBeanList){ this.mFragment = fragment; this.mDuanziBeanList = duanziBeanList; } @Override public DuanziViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_duanzi, null); return new DuanziViewHolder(view); } @Override public void onBindViewHolder(DuanziViewHolder holder, int position) { try { DuanziBean duanziBean = mDuanziBeanList.get(position); Glide.with(mFragment).load(duanziBean.getGroupBean().getUser().getAvatar_url()).into(holder.mCivAvatar); holder.mTvContent.setText(duanziBean.getGroupBean().getText()); holder.mTvAuthor.setText(duanziBean.getGroupBean().getUser().getName()); } catch (Exception e) { e.printStackTrace(); } } @Override public int getItemCount() { return mDuanziBeanList.size(); } public static class DuanziViewHolder extends RecyclerView.ViewHolder{ private CircleImageView mCivAvatar; private TextView mTvAuthor; private TextView mTvContent; public DuanziViewHolder(View itemView) { super(itemView); mCivAvatar = (CircleImageView) itemView.findViewById(R.id.duanzi_civ_avatar); mTvAuthor = (TextView) itemView.findViewById(R.id.duanzi_tv_author); mTvContent = (TextView) itemView.findViewById(R.id.duanzi_tv_content); } }}
最後段子頁面中進行資料和擷取以及介面的初始化
public class DuanziFragment extends Fragment { @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_duanzi, container, false); ButterKnife.bind(this, view); initView(); initRefresh(); return view; } private void initRefresh() { mRefresh.setColorSchemeResources(R.color.colorPrimary); mRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { initView(); mRefresh.setRefreshing(false); } }); } private void initView() { VolleyHelper.sendHttpGet(getActivity(), DuanziApi.GET_DUANZI, new VolleyResponseCallback() { @Override public void onSuccess(String response) { List<DuanziBean> mDuanziBeanList = GsonHelper.getDuanziBeanList(response); mDuanziBeanList.remove(3); mRvShowDuanzi.setLayoutManager(new LinearLayoutManager(getActivity())); mRvShowDuanzi.setAdapter(new DuanziAdapter(DuanziFragment.this, mDuanziBeanList)); } @Override public void onError(VolleyError error) { Logger.d(error); } }); }}
以上便是本文的全部內容,這個 APP 的全部代碼我已經分享到 Github 上了,如果覺得對你有協助的話,就賞個 star 吧。
猜你喜歡
- Android 一款十分簡潔、優雅的日記 APP
- Android 能讓你少走彎路的乾貨整理
- Android 擼起袖子,自己封裝 DialogFragment
手把手教你從零開始做一個好看的 APP