編輯:關於Android編程
根據原型圖,我們可以看出,UI分為兩部分,底部Tab導航+上方列表顯示。 所以此處,我們通過 FragmentTabHost+Fragment,來實現底部的導航頁面,通過RecyclerView來實現列表頁面。
因為篇幅原因,關於FragmentTabHost和RecyclerView的使用,不多做介紹,可以建議參考: FragmentTabHost使用方法及RecycleView_PullToRefresh_LoadMore兩篇文章,其中後者關於Recyclerview的項目是我之前封裝的一個支持下拉刷新,加載更多,添加Header和Footer等功能的RecyclerView,便於使用。
此處,再多說一點,因為是我們做自己來實現該ui,沒美工給我設計圖,切圖標, 所以我們需要自己去找圖標,此處推薦Iconfont-阿裡巴巴矢量圖標庫, 在這裡,我們可以找到很多的圖標,選擇適用的幾個即可。
篇幅原因,具體的頁面布局、實現代碼,我這裡就不多貼,有興趣的,可以直接看源碼,此處,只貼出列表list_item的頁面布局代碼。
從原型圖中,我們可以看出,列表的顯示分為純文顯示和圖片顯示,所以我們的item布局,應該要兼容這兩種顯示方式。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">
效果圖:
我們只需要在實現的邏輯上,控制文字、圖片的顯隱就好了。
在第一章捧腹網網頁分析、數據獲取中,我已經講過了如何去解析網頁中的數據為我們所用,拿到數據後,我們需要用這些數據填充RecyclerView,此處,使用的是我已經封裝好的RecyclerView,支持翻頁加載數據。
import android.support.v4.app.Fragment; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.util.DisplayMetrics; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; import com.bumptech.glide.Glide; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.lnyp.joke.R; import com.lnyp.joke.pengfu.JokeBean; import com.lnyp.joke.widget.CircleImageView; import com.lnyp.joke.widget.ShowMaxImageView; import java.util.List; import butterknife.BindView; import butterknife.ButterKnife; /** *笑話列表 */ public class JokeListAdapter extends RecyclerView.Adapter{ private LayoutInflater mInflater; private Fragment mContext; private List mDatas; private View.OnClickListener onItemClick; private int screenWidth; public JokeListAdapter(Fragment context, List datas, View.OnClickListener onItemClick) { this.mContext = context; this.mDatas = datas; this.onItemClick = onItemClick; mInflater = LayoutInflater.from(context.getActivity()); DisplayMetrics metric = new DisplayMetrics(); context.getActivity().getWindowManager().getDefaultDisplay().getMetrics(metric); screenWidth = metric.widthPixels; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = mInflater.inflate(R.layout.list_item_joke, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { ViewHolder viewHolder = (ViewHolder) holder; JokeBean jokeBean = mDatas.get(position); if (jokeBean != null) { Glide.with(mContext) .load(jokeBean.getUserAvatar()) .asBitmap() .centerCrop() .into(viewHolder.imgUser); viewHolder.textUserName.setText(jokeBean.getUserName()); viewHolder.textLastTime.setText(jokeBean.getLastTime()); viewHolder.textTitle.setText(jokeBean.getTitle()); JokeBean.DataBean dataBean = jokeBean.getDataBean(); if (dataBean != null) { if (TextUtils.isEmpty(dataBean.getContent())) { viewHolder.textContent.setVisibility(View.GONE); viewHolder.imgJoke.setVisibility(View.VISIBLE); viewHolder.textTitle.setVisibility(View.VISIBLE); // System.out.println(dataBean.getShowImg() + " " + dataBean.getGifsrcImg()); double width = Double.parseDouble(dataBean.getWidth()); double height = Double.parseDouble(dataBean.getHeight()); ViewGroup.LayoutParams lp = viewHolder.imgJoke.getLayoutParams(); lp.width = (int) (screenWidth * 0.8); lp.height = (int) (screenWidth * 0.8 * height / width); viewHolder.imgJoke.setLayoutParams(lp); String url = dataBean.getShowImg(); String gifUrl = dataBean.getGifsrcImg(); System.out.println("url : " + url + " gifUrl : " + gifUrl); if (TextUtils.isEmpty(gifUrl)) { Glide.with(mContext).load(url).asBitmap().diskCacheStrategy(DiskCacheStrategy.SOURCE).into(viewHolder.imgJoke); } else { Glide.with(mContext).load(gifUrl).asGif().diskCacheStrategy(DiskCacheStrategy.SOURCE).into(viewHolder.imgJoke); } } else { viewHolder.textContent.setVisibility(View.VISIBLE); viewHolder.imgJoke.setVisibility(View.GONE); viewHolder.textTitle.setVisibility(View.GONE); viewHolder.textContent.setText(dataBean.getContent()); } } List tags = jokeBean.getTags(); if (tags != null) { int size = tags.size(); if (size == 0) { updateTags(viewHolder, View.GONE, View.GONE, View.GONE, View.GONE); } else if (size == 1) { viewHolder.textTag1.setText(tags.get(0)); updateTags(viewHolder, View.VISIBLE, View.GONE, View.GONE, View.GONE); } else if (size == 2) { viewHolder.textTag1.setText(tags.get(0)); viewHolder.textTag2.setText(tags.get(1)); updateTags(viewHolder, View.VISIBLE, View.VISIBLE, View.GONE, View.GONE); } else if (size == 3) { viewHolder.textTag1.setText(tags.get(0)); viewHolder.textTag2.setText(tags.get(1)); viewHolder.textTag3.setText(tags.get(2)); updateTags(viewHolder, View.VISIBLE, View.VISIBLE, View.VISIBLE, View.GONE); } else { viewHolder.textTag1.setText(tags.get(0)); viewHolder.textTag2.setText(tags.get(1)); viewHolder.textTag3.setText(tags.get(2)); viewHolder.textTag4.setText(tags.get(3)); updateTags(viewHolder, View.VISIBLE, View.VISIBLE, View.VISIBLE, View.VISIBLE); } viewHolder.layoutTags.setVisibility(View.VISIBLE); } else { updateTags(viewHolder, View.GONE, View.GONE, View.GONE, View.GONE); viewHolder.layoutTags.setVisibility(View.GONE); } viewHolder.imgJoke.setTag(R.string.app_name, position); viewHolder.imgJoke.setOnClickListener(onItemClick); } } private void updateTags(ViewHolder viewHolder, int v1, int v2, int v3, int v4) { viewHolder.textTag1.setVisibility(v1); viewHolder.textTag2.setVisibility(v2); viewHolder.textTag3.setVisibility(v3); viewHolder.textTag4.setVisibility(v4); } @Override public int getItemCount() { return mDatas != null ? mDatas.size() : 0; } class ViewHolder extends RecyclerView.ViewHolder { @BindView(R.id.imgJoke) public ShowMaxImageView imgJoke; @BindView(R.id.textContent) public TextView textContent; @BindView(R.id.layoutTags) public LinearLayout layoutTags; @BindView(R.id.textTitle) public TextView textTitle; @BindView(R.id.textTag1) public TextView textTag1; @BindView(R.id.textTag2) public TextView textTag2; @BindView(R.id.textTag3) public TextView textTag3; @BindView(R.id.textTag4) public TextView textTag4; @BindView(R.id.imgUser) public CircleImageView imgUser; @BindView(R.id.textUserName) public TextView textUserName; @BindView(R.id.textLastTime) public TextView textLastTime; public ViewHolder(View itemView) { super(itemView); ButterKnife.bind(this, itemView); } } }
對於RecyclerView的適配器RecyclerView.Adapter的使用方式,相信玩過它的人都很熟悉,裡面的方法不多介紹,主要講下圖片處理這塊的實現:
String url = dataBean.getShowImg(); String gifUrl = dataBean.getGifsrcImg(); System.out.println("url : " + url + " gifUrl : " + gifUrl); if (TextUtils.isEmpty(gifUrl)) { Glide.with(mContext).load(url).asBitmap().diskCacheStrategy(DiskCacheStrategy.SOURCE).into(viewHolder.imgJoke); } else { Glide.with(mContext).load(gifUrl).asGif().diskCacheStrategy(DiskCacheStrategy.SOURCE).into(viewHolder.imgJoke); }
app中要展示的圖片,分為靜態圖片和動態圖片,glide可以很好的處理gif動態圖的加載,但是,如果像下面這樣直接使用glide加載動態圖,效率可是比較慢的。
Glide.with(mContext).load(gifUrl)into(viewHolder.imgJoke);
所以,在加載gif動態圖的時候,我們通常使用下面這樣的緩存策略
Glide.with(mContext).load(gifUrl).asGif().diskCacheStrategy(DiskCacheStrategy.SOURCE).into(viewHolder.imgJoke);
除了圖片加載,還有一點需要講解下,就是下面這段代碼:
double width = Double.parseDouble(dataBean.getWidth()); double height = Double.parseDouble(dataBean.getHeight()); ViewGroup.LayoutParams lp = viewHolder.imgJoke.getLayoutParams(); lp.width = (int) (screenWidth * 0.8); lp.height = (int) (screenWidth * 0.8 * height / width); viewHolder.imgJoke.setLayoutParams(lp);
這段代碼的意思是,在加載圖片之前,先設置了ImageView的寬高。這樣做的目的,是為了在圖片加載顯示之前就固定ImageView的大小,避免了列表因為圖片高度不一致而出現“晃動”。
好,到這裡,我們基本完成了列表顯示功能了,接下來,在Fragment中實現功能邏輯。
package com.lnyp.joke.fragment; import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.support.v4.app.Fragment; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.baoyz.widget.PullRefreshLayout; import com.lnyp.flexibledivider.HorizontalDividerItemDecoration; import com.lnyp.joke.R; import com.lnyp.joke.adapter.JokeListAdapter; import com.lnyp.joke.http.HttpUtils; import com.lnyp.joke.pengfu.JokeApi; import com.lnyp.joke.pengfu.JokeBean; import com.lnyp.joke.pengfu.JokeUtil; import com.lnyp.joke.widget.SmartisanDrawable; import com.lnyp.recyclerview.EndlessRecyclerOnScrollListener; import com.lnyp.recyclerview.HeaderAndFooterRecyclerViewAdapter; import com.lnyp.recyclerview.RecyclerViewLoadingFooter; import com.lnyp.recyclerview.RecyclerViewStateUtils; import com.victor.loading.rotate.RotateLoading; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import java.util.ArrayList; import java.util.List; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.Unbinder; public class MainFragment extends Fragment { private Unbinder unbinder; @BindView(R.id.rotateloading) public RotateLoading rotateloading; @BindView(R.id.swipeRefreshLayout) public PullRefreshLayout swipeRefreshLayout; @BindView(R.id.listInspirations) public RecyclerView listInspirations; private HeaderAndFooterRecyclerViewAdapter mAdapter; private ListmDatas; private int page = 1; private boolean mHasMore = false; private boolean isRefresh = true; // 處理請求返回信息 private MyHandler mHandler = new MyHandler(); private class MyHandler extends Handler { public void handleMessage(android.os.Message msg) { switch (msg.what) { case 0: RecyclerViewStateUtils.setFooterViewState(listInspirations, RecyclerViewLoadingFooter.State.Normal); swipeRefreshLayout.setRefreshing(false); rotateloading.stop(); mAdapter.notifyDataSetChanged(); break; } } } public MainFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_main, container, false); unbinder = ButterKnife.bind(this, view); initView(); rotateloading.start(); refreshReq(); return view; } private void initView() { mDatas = new ArrayList<>(); JokeListAdapter jokeListAdapter = new JokeListAdapter(this, mDatas, onClickListener); mAdapter = new HeaderAndFooterRecyclerViewAdapter(jokeListAdapter); listInspirations.setAdapter(mAdapter); listInspirations.setLayoutManager(new LinearLayoutManager(getActivity())); listInspirations.addItemDecoration( new HorizontalDividerItemDecoration.Builder(getActivity()) .colorResId(R.color.divider_color) .build()); listInspirations.addOnScrollListener(mOnScrollListener); swipeRefreshLayout.setOnRefreshListener(onRefreshListener); swipeRefreshLayout.setRefreshDrawable(new SmartisanDrawable(getActivity(), swipeRefreshLayout)); swipeRefreshLayout.setBackgroundColor(Color.parseColor("#EFEFEF")); swipeRefreshLayout.setColor(Color.parseColor("#8F8F81")); } private PullRefreshLayout.OnRefreshListener onRefreshListener = new PullRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { refreshReq(); } }; private void refreshReq() { isRefresh = true; page = 1; qryJokes(); } private void qryJokes() { final String url = JokeApi.PENGFU_NEW_JOKES + page + JokeApi.URL_SUFFIX; System.out.println(url); HttpUtils.doGetAsyn(url, new HttpUtils.CallBack() { @Override public void onRequestComplete(String result) { if (result == null) { return; } System.out.println(result); Document doc = Jsoup.parse(result); if (doc != null) { JokeUtil jokeUtil = new JokeUtil(); List jokeBeens = jokeUtil.getNewJokelist(doc); if (jokeBeens != null) { page++; mHasMore = true; if (isRefresh) { mDatas.clear(); isRefresh = false; } mDatas.addAll(jokeBeens); mHandler.sendEmptyMessage(0); } } } }); } private EndlessRecyclerOnScrollListener mOnScrollListener = new EndlessRecyclerOnScrollListener() { @Override public void onLoadNextPage(View view) { super.onLoadNextPage(view); RecyclerViewLoadingFooter.State state = RecyclerViewStateUtils.getFooterViewState(listInspirations); if (state == RecyclerViewLoadingFooter.State.Loading) { return; } if (mHasMore) { RecyclerViewStateUtils.setFooterViewState(getActivity(), listInspirations, mHasMore, RecyclerViewLoadingFooter.State.Loading, null); qryJokes(); } else { RecyclerViewStateUtils.setFooterViewState(getActivity(), listInspirations, mHasMore, RecyclerViewLoadingFooter.State.TheEnd, null); } } }; private View.OnClickListener onClickListener = new View.OnClickListener() { @Override public void onClick(View view) { try { int pos = (int) view.getTag(R.string.app_name); JokeBean jokeBean = mDatas.get(pos); String showImg = jokeBean.getDataBean().getShowImg(); String gifSrcImg = jokeBean.getDataBean().getGifsrcImg(); // System.out.println(showImg + " " + gifSrcImg); } catch (Exception e) { e.printStackTrace(); } } }; @Override public void onDestroyView() { super.onDestroyView(); unbinder.unbind(); } }
功能邏輯比較簡單,不多做解釋。 到這裡,我們的“捧腹”APP已經完成了80%了。下面,在做些擴展性的功能,使得它更像一個完整的APP。
上篇博文,我們就提到了,我們要使用PhotoView實現大圖的浏覽功能。
PhotoView的使用,可以直接在它的github官方介紹上看到,下面,我直接貼出使用代碼。
import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.text.TextUtils; import android.view.View; import android.widget.ImageView; import com.bumptech.glide.Glide; import com.bumptech.glide.load.engine.DiskCacheStrategy; import butterknife.BindView; import butterknife.ButterKnife; import uk.co.senab.photoview.PhotoViewAttacher; /** * 圖片浏覽 */ public class PhotoActivity extends FragmentActivity { @BindView(R.id.imgJoke) public ImageView imgJoke; private String showImg; private String gifSrcImg; private PhotoViewAttacher mAttacher; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_photo); ButterKnife.bind(this); mAttacher = new PhotoViewAttacher(imgJoke); showImg = getIntent().getStringExtra("showImg"); gifSrcImg = getIntent().getStringExtra("gifSrcImg"); System.out.println(showImg + " " + gifSrcImg); if (TextUtils.isEmpty(gifSrcImg)) { Glide.with(this).load(showImg).asBitmap().diskCacheStrategy(DiskCacheStrategy.SOURCE).into(imgJoke); } else { Glide.with(this).load(gifSrcImg).asGif().diskCacheStrategy(DiskCacheStrategy.SOURCE).into(imgJoke); } mAttacher.update(); mAttacher.setOnPhotoTapListener(new PhotoViewAttacher.OnPhotoTapListener() { @Override public void onPhotoTap(View view, float x, float y) { PhotoActivity.this.finish(); } @Override public void onOutsidePhotoTap() { PhotoActivity.this.finish(); } }); } }
程序中,我們添加了一個事件監聽,主要是為了在用戶單擊圖片顯示或者不顯示部分時,可以退出浏覽。
功能做到這裡,基本上完成了90%的APP,接下來,我們為其加入崩潰分析、版本更新功能。
關於如何配置,大家直接到http://bughd.com/doc/android官網看,看官方文檔,為app添加功能,是開發的基本能力,而且,這個功能集成並不困難,建議大家自己添加,有疑問,可參考我最後放出的源碼。
截止此處,我們的“捧腹”APP基本上就已經實現了,在說打包發布之前,我要提到一個很重要的問題,那就是數據版權。 我們知道,這個app的數據,是分析“捧腹網”的網頁,拿到的,我們應當尊重其版權所有。 因為我們是學習使用,所以大家應在app明顯的位置,加上數據來源。這裡,我選擇在啟動頁面上添加。
接下來,就是打包發布了。關於如何打包app,限於篇幅,請參考我之前寫的 Android Studio(十二):打包多個發布渠道的apk文件 ,打包apk成功後,我們將其發布在fir.im免費托管分發服務的平台上,方便大家下載測試。(如果沒問題,可以上傳到應用市場)。
最後,讓我們看下APP最終效果。
項目小結:
如果你能耐下心來,看完這三篇實戰博文,相信你也可以做一個簡單的app了。這個app實現並不難,博文講解的也算是詳盡,很容易理解。
這系列的博文,主要是針對初中級開發者,幫大家在研發過程中,理清思路,一步步完成一個完整的app。希望看完這篇博文的朋友,也能夠舉一反三, 做出一個自己所屬的app。
源碼地址:https://github.com/zuiwuyuan/Joke
歡迎有問題的朋友,留言討論,也歡迎進QQ群來討論交流:487786925( Android研發村 ),謝謝大家的支持。
本文實例講述了Android基於service實現音樂的後台播放功能。分享給大家供大家參考,具體如下:Service是一個生命周期長且沒有用戶界面的程序,當程序在各個ac
支付接入流程官方文檔https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_5項目的應用(AppRegi
某些參數總是同時出現,可能好幾個方法都使用這樣一組參數,為了避免參數列表過長,同時也為了避免重復代碼, 我們可以將這些參數提煉為參數對象,原來傳入參數的地方使用這個參數對
新版的微信裡,細心的朋友應該能看到在搜索那裡多出了一個功能:朋友圈熱文。隨時了解朋友圈的熱門文章,不過有不少人都很疑惑微信朋友圈熱文要怎麼查看,下面就讓小編