手把手教你從零開始做一個好看的 APP

來源:互聯網
上載者:User

標籤: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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.