Android MVP,OkHttp,ImageLoader/Glide,介面回撥,RecyclerView,屬性動畫,自定義View/ViewGroup,Sqlite
(一) 業務需求描述
1. 完成如下三色梯頁面效果
1)自定義ViewGroup的方式完成如圖一三色梯頁面效果,第一條紅色,第二條綠色,第三條藍色,依次迴圈,臺階上顯示第幾條臺階數,每臺階梯子佔控制元件寬度的1/3,垂直方向依次向下新增
2)為自定義三色梯提供新增和刪除的方法,條目提供長按和點選事件
3)當點選標題欄右上方的新增按鈕時,新的臺階新增到三色梯的下方,並顯示新增的條數
4)長按三色梯的條目,可以刪除選中條目
5)當點選新增的時候,使用屬性動畫,將內容平移到三色梯的條目上
6)點選條目時,將當前條目序號傳到新聞列表頁面
2. 完成圖三所示新聞列表頁面
1)使用OkHttp做網路請求,使用單例模式封裝OkHttp,包括Get請求和Post請求,新增日誌攔截器,自定義回撥介面並回調到主執行緒
2)使用MVP框架搭建,分包明確,V層和M層解耦,通過介面完成V層和P層以及P層和M層通訊,解決記憶體洩漏問題
3)使用xRecyClerView做列表展示頁面,並實現下拉重新整理,上拉載入更多的功能,使用ListView類的控制元件不得分
4)根據條目資料中的序號來實現多條目載入,每兩條顯示3張圖片,第三條顯示1張圖片,依次排列,一張圖片時顯示在條目左側,兩條或三條時顯示在條目下方,圖片水平排列
5)自行選擇圖片載入框架完成圖片載入
6)使用SQLite對新聞條目進行快取,進入頁面時,先從資料庫進行讀取資料展示,當有網時,從網路載入資料並更新列表資料,沒有網路時,彈出吐司提示。
7)長按條目,彈出確認刪除對話方塊,點選確認按鈕時移除所選條目,新增預設的刪除動畫,並區域性重新整理
8)當確認刪除後,將快取到資料庫的條目狀態標記為已刪除,下次從資料庫讀取資料時,被標記為已刪除的條目不再進行展示,並且從網路請求資料時,如果載入的資料已經被刪除過,則不展示該已經刪除過的資料
(二) 效果圖
(三) 技術選型
1. MVP;
2. OKHttp;
3. ImageLoader/Glide等
4. 介面回撥;
5. RecyclerView
6. 屬性動畫
7. 自定義View/ViewGroup
8. Sqlite
(四) 思路分解
1. 通過文字或流程圖等分析需求,形成完整解決問題思路;
2. 提供文件或流程圖,一起拷貝到u盤;
(五) 介面
http://ttpc.dftoutiao.com/jsonpc/refresh?type=5010
其中,type是5010+梯子序號,比如點選的第一個臺階,則type=5011,第二個臺階,則type=5012,依次類推,當下拉重新整理時,type為剛開始的值,當上拉載入更多時,type值加1
(六) 第三方依賴
OkHttp:
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
OkHttp日誌攔截器:
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
xRecyclerView:
compile 'com.jcodecraeer:xrecyclerview:1.5.9'
Glide:
implementation'com.github.bumptech.glide:glide:4.7.1'
annotationProcessor'com.github.bumptech.glide:compiler:4.7.1'
ImageLoader:
素材中提供Jar包
NewsAdapter
package com.example.kson.monthdemo.adapter; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.provider.ContactsContract; import android.support.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import com.bumptech.glide.Glide; import com.example.kson.monthdemo.MainActivity; import com.example.kson.monthdemo.R; import com.example.kson.monthdemo.bean.News; import com.example.kson.monthdemo.common.Constants; import com.google.gson.Gson; import com.jcodecraeer.xrecyclerview.XRecyclerView; import java.util.List; /** * Author:kson * E-mail:[email protected] * Time:2018/05/29 * Description: */ public class NewsAdapter extends XRecyclerView.Adapter<XRecyclerView.ViewHolder> { private List<News.Data> list; private Context context; //private News news; public NewsAdapter(List<News.Data> list, Context context) { this.list = list; this.context = context; //this.news = news; } public void loadMore(List<News.Data> data) { if (list != null) { list.addAll(data); notifyDataSetChanged(); } } @NonNull @Override public XRecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { if (viewType == Constants.TYPE1) { View view = LayoutInflater.from(context).inflate(R.layout.news_item2_layout, parent, false); return new Type1ViewHolder(view); } else { View view2 = LayoutInflater.from(context).inflate(R.layout.news_item_layout, parent, false); return new Type2ViewHolder(view2); } } @Override public void onBindViewHolder(@NonNull final XRecyclerView.ViewHolder holder, int position) { holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View view) { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle("刪除"); builder.setNegativeButton("確定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { int pos = holder.getLayoutPosition()-1;//得到下標 System.out.println("pos----"+pos); list.remove(pos);//刪除集合的資料 /*news.data = list; String json = new Gson().toJson(news);*/ notifyItemRemoved(pos);//區域性刪除當前view並區域性重新整理 } }); builder.setNeutralButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { } }); builder.show(); return true; } }); News.Data data = list.get(position); if (holder instanceof Type1ViewHolder) {//1張圖片 ((Type1ViewHolder) holder).title.setText(data.topic); } else if (holder instanceof Type2ViewHolder) {//三張圖片 ((Type2ViewHolder) holder).title.setText(data.topic); if (data.miniimg != null && data.miniimg.size() > 0) { if (data.miniimg.size() == 1) { Glide.with(context).load(data.miniimg.get(0).src).into(((Type2ViewHolder) holder).iv1); Glide.with(context).load(data.miniimg.get(0).src).into(((Type2ViewHolder) holder).iv2); Glide.with(context).load(data.miniimg.get(0).src).into(((Type2ViewHolder) holder).iv3); } else if (data.miniimg.size() == 2) { Glide.with(context).load(data.miniimg.get(0).src).into(((Type2ViewHolder) holder).iv1); Glide.with(context).load(data.miniimg.get(1).src).into(((Type2ViewHolder) holder).iv2); Glide.with(context).load(data.miniimg.get(1).src).into(((Type2ViewHolder) holder).iv3); } else { Glide.with(context).load(data.miniimg.get(0).src).into(((Type2ViewHolder) holder).iv1); Glide.with(context).load(data.miniimg.get(1).src).into(((Type2ViewHolder) holder).iv2); Glide.with(context).load(data.miniimg.get(2).src).into(((Type2ViewHolder) holder).iv3); } } } } @Override public int getItemViewType(int position) { return position % 2 == 0 ? Constants.TYPE1 : Constants.TYPE2; } @Override public int getItemCount() { return list.size(); } class Type1ViewHolder extends XRecyclerView.ViewHolder { private TextView title; public Type1ViewHolder(View itemView) { super(itemView); title = itemView.findViewById(R.id.title1); } } class Type2ViewHolder extends XRecyclerView.ViewHolder { private TextView title; private ImageView iv1, iv2, iv3; public Type2ViewHolder(View itemView) { super(itemView); iv1 = itemView.findViewById(R.id.img1); iv2 = itemView.findViewById(R.id.img2); iv3 = itemView.findViewById(R.id.img3); title = itemView.findViewById(R.id.title3); } } }LocalNews
package com.example.kson.monthdemo.bean;
/** * Author:kson * E-mail:[email protected] * Time:2018/05/30 * Description: */ public class LocalNews { public String title; public String imgurls; public String source; public String time; public boolean isDel; }
News
package com.example.kson.monthdemo.bean;
import java.util.List; /** * Author:kson * E-mail:[email protected] * Time:2018/05/29 * Description: */ public class News { public String stat; public List<Data> data; public class Data { public String topic; public String source; public List<IMG> miniimg; public class IMG { public String src; } } }
Constants
package com.example.kson.monthdemo.common;
/** * Author:kson * E-mail:[email protected] * Time:2018/05/29 * Description: */ public class Constants { public static final String GET_URL = "http://ttpc.dftoutiao.com/jsonpc/refresh"; public static final int TYPE1 = 3;//條目是三張圖 public static final int TYPE2 = 1;//條目是1張圖 }
DbHelper
package com.example.kson.monthdemo.db;
import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; /** * Author:kson * E-mail:[email protected] * Time:2018/05/30 * Description: */ public class DbHelper extends SQLiteOpenHelper { //資料庫檔名稱 private static final String DB_NAME = "news.db"; public static final String NEWS_TABLE_NAME = "news"; private static final int VERSION = 1; public DbHelper(Context context) { super(context, DB_NAME, null, VERSION); } @Override public void onCreate(SQLiteDatabase sqLiteDatabase) { String sql = "create table " + NEWS_TABLE_NAME + " (_id Integer PRIMARY KEY ,json text)"; sqLiteDatabase.execSQL(sql); } @Override public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { } }
NewsModel
package com.example.kson.monthdemo.model;
import com.example.kson.monthdemo.db.DbHelper; import com.example.kson.monthdemo.utils.OkhttpUtils; import java.util.Map; /** * Author:kson * E-mail:[email protected] * Time:2018/05/29 * Description: */ public class NewsModel { /** * 請求資料 * @param getUrl */ public void getData(String getUrl, Map<String,String> params,final ResponseCallback responseCallback) { OkhttpUtils.getInstance().postData(getUrl,params, new OkhttpUtils.ICallback() { @Override public void success(String result) { responseCallback.success(result); } @Override public void fail(String msg) { responseCallback.fail(msg); } }); } public interface ResponseCallback{ void success(String result); void fail(String msg); } }
NewsPresenter
package com.example.kson.monthdemo.presenter; import android.text.TextUtils; import com.example.kson.monthdemo.MainActivity; import com.example.kson.monthdemo.bean.News; import com.example.kson.monthdemo.db.DbHelper; import com.example.kson.monthdemo.model.NewsModel; import com.example.kson.monthdemo.utils.OkhttpUtils; import com.example.kson.monthdemo.view.INews; import com.google.gson.Gson; import java.util.Map; /** * Author:kson * E-mail:[email protected] * Time:2018/05/29 * Description:p */ public class NewsPresenter { private INews iNews; private NewsModel model; public NewsPresenter(INews iNews) { model = new NewsModel(); attach(iNews); } /** * 繫結view * @param iNews */ public void attach(INews iNews){ this.iNews = iNews; } /** * 獲取資料的方法 * * @param getUrl */ public void getData(String getUrl, Map<String ,String> params) { model.getData(getUrl, params,new NewsModel.ResponseCallback() { @Override public void success(String result) { if (!TextUtils.isEmpty(result)) { String s = result.replace("null(","") .replace(")",""); News news = new Gson().fromJson(s, News.class); iNews.success(news); } } @Override public void fail(String msg) { } }); // OkhttpUtils.getInstance().getData(getUrl, new OkhttpUtils.ICallback() { // @Override // public void success(String result) { // // } // // @Override // public void fail(String msg) { // // } // }); } /** * 解綁 */ public void detach(){ this.iNews = null; } }
AppUtil
package com.example.kson.monthdemo.utils; import android.content.Context; import android.util.DisplayMetrics; /** * Author:kson * E-mail:[email protected] * Time:2018/05/30 * Description: */ public class AppUtil { /** * * @param context * @return 螢幕寬度 */ public static int screenWidth(Context context){ DisplayMetrics metrics = context.getResources().getDisplayMetrics(); return metrics.widthPixels; } }
NetWorkUtil
package com.example.kson.monthdemo.utils; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; /** * Author:kson * E-mail:[email protected] * Time:2018/05/30 * Description:判斷網路 */ public class NetWorkUtil { /** * * @return 是否有活動的網路連線 */ public final static boolean hasNetWorkConnection(Context context){ //獲取連線活動管理器 final ConnectivityManager connectivityManager= (ConnectivityManager) context. getSystemService(Context.CONNECTIVITY_SERVICE); //獲取連結網路資訊 final NetworkInfo networkInfo=connectivityManager.getActiveNetworkInfo(); return (networkInfo!= null && networkInfo.isAvailable()); } /** * @return 返回boolean ,是否為wifi網路 * */ public final static boolean hasWifiConnection(Context context) { final ConnectivityManager connectivityManager= (ConnectivityManager) context. getSystemService(Context.CONNECTIVITY_SERVICE); final NetworkInfo networkInfo=connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); //是否有網路並且已經連線 return (networkInfo!=null&& networkInfo.isConnectedOrConnecting()); } /** * @return 返回boolean,判斷網路是否可用,是否為行動網路 * */ public final static boolean hasGPRSConnection(Context context){ //獲取活動連線管理器 final ConnectivityManager connectivityManager= (ConnectivityManager) context. getSystemService(Context.CONNECTIVITY_SERVICE); final NetworkInfo networkInfo=connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); return (networkInfo!=null && networkInfo.isAvailable()); } /** * @return 判斷網路是否可用,並返回網路型別,ConnectivityManager.TYPE_WIFI,ConnectivityManager.TYPE_MOBILE,不可用返回-1 */ public static final int getNetWorkConnectionType(Context context){ final ConnectivityManager connectivityManager=(ConnectivityManager) context. getSystemService(Context.CONNECTIVITY_SERVICE); final NetworkInfo wifiNetworkInfo=connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); final NetworkInfo mobileNetworkInfo=connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); if(wifiNetworkInfo!=null &&wifiNetworkInfo.isAvailable()) { return ConnectivityManager.TYPE_WIFI; } else if(mobileNetworkInfo!=null &&mobileNetworkInfo.isAvailable()) { return ConnectivityManager.TYPE_MOBILE; } else { return -1; } } }
OkhttpUtils
package com.example.kson.monthdemo.utils; import android.content.Context; import android.os.Handler; import java.io.IOException; import java.util.List; import java.util.Map; import okhttp3.Call; import okhttp3.Callback; import okhttp3.FormBody; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.logging.HttpLoggingInterceptor; /** * Author:kson * E-mail:[email protected] * Time:2018/05/29 * Description: */ public class OkhttpUtils { private static OkhttpUtils okhttpUtils; private OkHttpClient okHttpClient; private Handler handler; private OkhttpUtils() { okHttpClient = new OkHttpClient.Builder() .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) .build(); handler = new Handler(); } public static OkhttpUtils getInstance() { if (okhttpUtils == null) { okhttpUtils = new OkhttpUtils(); } return okhttpUtils; } /** * get方式 */ public void getData(String url, final ICallback callback){ final Request request = new Request.Builder() .url(url).build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { if (callback!=null){ handler.post(new Runnable() { @Override public void run() { callback.fail("請求失敗"); } }); } } @Override public void onResponse(Call call, Response response) throws IOException { if (callback!=null){ if (response.isSuccessful()&&response.code()==200){ final String result = response.body().string(); handler.post(new Runnable() { @Override public void run() { callback.success(result); } }); } } } }); } /** * post方式 */ public void postData(String url, Map<String,String> params, final ICallback callback){ FormBody.Builder builder = new FormBody.Builder(); for (Map.Entry<String, String> bean : params.entrySet()) { builder.add(bean.getKey(),bean.getValue()); } final Request request = new Request.Builder() .url(url).post(builder.build()).build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { if (callback!=null){ handler.post(new Runnable() { @Override public void run() { callback.fail("請求失敗"); } }); } } @Override public void onResponse(Call call, Response response) throws IOException { if (callback!=null){ if (response.isSuccessful()&&response.code()==200){ final String result = response.body().string(); handler.post(new Runnable() { @Override public void run() { callback.success(result); } }); } } } }); } public interface ICallback{ void success(String result); void fail(String msg); } }
INews
package com.example.kson.monthdemo.view; import com.example.kson.monthdemo.bean.News; /** * Author:kson * E-mail:[email protected] * Time:2018/05/29 * Description: */ public interface INews { void success(News news); }
ThreeColorView
package com.example.kson.monthdemo.widget; import <