本項目來自菜鳥窩,有興趣者點擊http://www.cniao5.com/course/
項目已經做完,
https://github.com/15829238397/CN5E-shop
仿京東商城系列0------項目簡介
仿京東商城系列1------fragmentTabHost實現底部導航欄
仿京東商城系列2------自定義toolbar
仿京東商城系列3------封裝Okhttp
仿京東商城系列4------輪播廣告條
仿京東商城系列5------商品推薦欄
仿京東商城系列6------下拉刷新上拉加載的商品列表
仿京東商城系列7------商品分類頁面
仿京東商城系列8------自定義的數量控制器
仿京東商城系列9------購物車數據存儲器實現
仿京東商城系列10------添加購物車,管理購物車功能實現
仿京東商城系列11------商品排序功能以及布局切換實現(Tablayout)
仿京東商城系列12------商品詳細信息展示(nativie與html交互)
仿京東商城系列13------商品分享(shareSDK)
仿京東商城系列14------用戶登錄以及app登錄攔截
仿京東長城系列15------用戶注冊,SMSSDK集成
仿京東商城系列16------支付SDK集成
仿京東商城系列17------支付功能實現
仿京東商城系列18------xml文件讀取(地址選擇器)
仿京東商城系列19------九宮格訂單展示
仿京東商城系列20------終章
前言
本篇文章所設計的效果,將用到前文講述到的Okhttp封裝工具,adapter封裝工具,以及本文新涉及到的,新的圖片加載工具,緩沖框架等等。廢話少說,來看效果。
內容
Fresco簡介
Fresco 的詳細介紹
- Fresco 是什么
首先,Fresco 是一款開源的圖片加載組件,也是目前最強大的圖片加載組件。
其次,Fresco 中設計有一個叫做 image pipeline 的模塊。它負責從網絡,從本地文件系統,本地資源加載圖片。 為了最大限度節省空間和CPU時間,它含有3級緩存設計(2級內存,1級文件)。
另外,還有一個特別的地方,Fresco 中設計有一個叫做 Drawees 模塊, 方便地顯示 loading 圖,當圖片不再顯示在屏幕上時,及時地釋放內存和空間占用。
同時,Fresco 支持 Android 2.3(API level 9)及其以上版本。
- Fresco 的獨特之處
Fresco 作為眾多開源圖片加載組件之一,可以受到廣大開發者的喜愛,自然有著它的獨特之處。
2.1 內存管理
一個沒有未壓縮的圖片,即 Android 中的 Bitmap,占用大量的內存。大的內存占用勢必引發更加頻繁的GC。 在5.0以下,GC 將會顯著地引發界面卡頓。
在5.0以下系統,Fresco 將圖片放到一個特別的內存區域,也就是 Ashmem (系統匿名共享內存)。當然,在圖片不顯示的時候,占用的內存會自動被釋放。 這會使得 APP 更加流暢,減少因圖片內存占用而引發的 OOM。
2.2 漸進式呈現圖片
Fresco 加載圖片時,可以實現漸進式呈現圖片,漸進式的 JPEG 圖片格式已經流行數年了,漸進式圖片格式先呈現大致的圖片輪廓,然后隨著圖片下載的繼續, 呈現逐漸清晰的圖片,這對于移動設備,尤其是慢網絡有極大的利好,可帶來更好的用戶體驗。
2.3 支持Gif圖和WebP格式
作為加載組件,Fresco 不僅支持簡單的 JPG、PNG 格式的圖片加載,還同時支持 Gif 和 WebP 格式的圖片加載,非常強大。
2.4 圖像的呈現方式
Fresco 的圖像呈現方式也很特別,可以自定義居中焦點(對人臉等圖片顯示非常有幫助),支持圓角圖,當然圓圈也行,并且下載失敗之后,點擊可以重新下載,特別是可以自定義占位圖,自定義 overlay, 或者進度條,同時可以指定用戶按壓時的 overlay。
2.5 圖像的加載
圖像的加載時很重要的一部分,Fresco 可以為同一個圖片指定不同的遠程路徑,或者使用已經存在本地緩存中的圖片,加載時先顯示一個低解析度的圖片,等高清圖下載完之后再顯示高清圖,等到加載完成時回調通知,并且對于本地圖,如有 EXIF 縮略圖,在大圖加載完成之前,可先顯示縮略圖。至于縮放或者旋轉圖片和處理已下載的圖片,Fresco 都是可以做到的。
Fresco 的基本使用
前面已經對 Fresco 做了非常詳細的介紹了,對于 Fresco 一定很感興趣吧,下面就開始使用它吧。
- 添加 Gradle 依賴
在使用 Fresco 之前,一定要記得先使用 Gradle 添加對 Fresco 的依賴,代碼如下。、
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:appcompat-v7:25.0.1'
compile 'com.facebook.fresco:fresco:0.12.0'
}
- 添加權限
加載網絡圖片時,需要一定的網絡權限,所以必須添加網絡權限。
<uses-permission android:name="android.permission.INTERNET"/>
- 初始化 Fresco
在加載圖片之前,你必須初始化 Fresco 類。你只需要調用 Fresco.initialize() 一次即可完成初始化,在 Application 里面做這件事再適合不過了(如下面的代碼),注意多次的調用初始化是無意義的。
public class CniaoApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Fresco.initialize(this,config);
}
}
下面就是需要在 AndroidManifest.xml 中指定相應的 Application 類。
<application
android:name=".CniaoApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
.......
</application>
- 添加 SimpleDraweeView
首先,在 xml 布局文件 Layout 中, 加入命名空間。
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fresco="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent">
......
</LinearLayout>
然后在布局中加入 SimpleDraweeView。
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_view"
android:layout_width="500dp"
android:layout_height="300dp"
fresco:placeholderImage="@drawable/default_loading"
/>
- 加載圖片
在 Activity/Fragment 中寫入圖片加載即可。
public class MainActivity extends AppCompatActivity {
private String img_url="http://img4q.duitang.com/uploads/item/201411/20/20141120132318_3eAuc.thumb.700_0.jpeg";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Uri uri = Uri.parse(img_url);
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.image_view);
draweeView.setImageURI(uri);
}
}
MaterialRefreshLayout簡介
MaterialRefreshLayout是github上的一個開源控件,這款組件在原本官方的控件 SwipeRefreshLayout 上做了擴展,不僅可以支持下拉刷新,還可以支持加載更多。
- 使用方式
- 添加依賴
compile 'com.cjj.materialrefeshlayout:library:1.3.0'
- 在布局文件中使用,本例使用了MaterialRefreshLayout+RecyclerView組合的方式,實現下拉刷新,加載更多功能,具體代碼如下:
<com.cjj.MaterialRefreshLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/materialRefreshLayout">
<android.support.v7.widget.RecyclerView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/resyclerView"></android.support.v7.widget.RecyclerView>
</com.cjj.MaterialRefreshLayout>
- 在Activity或者Fragment中進行使用。
3.1.實例化MaterialRefreshLayout和 RecyclerView
@ViewInject(R.id.recycler_view_hot)
private RecyclerView mRecyclerView;
@ViewInject(R.id.refresh)
private MaterialRefreshLayout materialRefreshLayout;
3.2.獲得數據,計算當前頁,總頁數和每頁的數目,用于MaterialRefreshLayout進行分頁處理
private int totlaPage = 1;
private int curPage = 1;
private int pageSize = 10;
private OkHttpHelper httpHelper = OkHttpHelper.getInstance();
private List<Wares> waresList = new ArrayList<>();
private void getData( ){
httpHelper.get(url, new SpotsCallBack<Page<Wares>>(getContext()) {
@Override
public void onSuccess(Response response, Page<Wares> waresPage) {
waresList = waresPage.getList();
curPage = waresPage.getCurrentPage();
pageSize = waresPage.getPageSize();
totlaPage = waresPage.getTotalPage();
showData();
}
@Override
public void onError(Response response, int code, Exception e) {
}
});
}
3.3.RecyclerView顯示數據,此時RecyclerView不知是按照之前的方式(設置適配器)來顯示數據了,而是按照不同的狀態來顯示數據
private static final int STATE_NORMAL = 0;
private static final int STATE_REFREN = 1;
private static final int STATE_MORE = 2;
private int state = STATE_NORMAL;
private void showData(){
switch (state){
case STATE_NORMAL:
//此處完成正常加載操作
break;
case STATE_REFREN:
//此處完成刷新操作
materialRefreshLayout.finishRefresh();
break;
case STATE_MORE:
//此處完成加載更多操作
materialRefreshLayout.finishRefreshLoadMore();
break;
}
}
3.4為MaterialRefreshLayout添加監聽器,來實現下拉刷新和上拉加載時的邏輯
private void initMaterialRefreshLayout(){
materialRefreshLayout.setLoadMore(true);
materialRefreshLayout.setMaterialRefreshListener(new MaterialRefreshListener() {
@Override
public void onRefresh(final MaterialRefreshLayout materialRefreshLayout) {
//refreshing...
refreshData();
}
@Override
public void onRefreshLoadMore(MaterialRefreshLayout materialRefreshLayout) {
//load more refreshing...
if(curPage <= totlaPage)
loadMore();
else {
Toast.makeText(getContext(), "沒有更多了...", Toast.LENGTH_LONG).show();
materialRefreshLayout.finishRefreshLoadMore();
}
}
});
}
public void refreshData(){
curPage = 1;
state = STATE_REFREN;
getData();
}
private void loadMore(){
curPage = curPage + 1;
state = STATE_MORE;
getData();
}
3.5適配器,用Fresco來加載圖片
public class HotWaresAdapter extends RecyclerView.Adapter<HotWaresAdapter.ViewHolder> {
private List<Wares> mDatas;
private LayoutInflater mInflater;
public HotWaresAdapter(List<Wares> wares){
mDatas = wares;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
mInflater = LayoutInflater.from(parent.getContext());
View view = mInflater.inflate(R.layout.template_hot_wares,null);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Wares wares = getData(position);
holder.draweeView.setImageURI(Uri.parse(wares.getImgUrl()));
holder.textTitle.setText(wares.getName());
holder.textPrice.setText("¥"+wares.getPrice());
}
public Wares getData(int position){
return mDatas.get(position);
}
public List<Wares> getDatas(){
return mDatas;
}
public void clearData(){
mDatas.clear();
notifyItemRangeRemoved(0,mDatas.size());
}
public void addData(List<Wares> datas){
addData(0,datas);
}
public void addData(int position,List<Wares> datas){
if(datas !=null && datas.size()>0) {
mDatas.addAll(datas);
notifyItemRangeChanged(position, mDatas.size());
}
}
@Override
public int getItemCount() {
if(mDatas!=null && mDatas.size()>0)
return mDatas.size();
return 0;
}
class ViewHolder extends RecyclerView.ViewHolder{
SimpleDraweeView draweeView;
TextView textTitle;
TextView textPrice;
public ViewHolder(View itemView) {
super(itemView);
draweeView = (SimpleDraweeView) itemView.findViewById(R.id.drawee_view);
textTitle= (TextView) itemView.findViewById(R.id.text_title);
textPrice= (TextView) itemView.findViewById(R.id.text_price);
}
}
}
封裝下拉刷新頁面功能
- 將分頁所需信息,封裝在一個數據類中
package com.example.shoppingstore.Bean;
import java.util.List;
/**
* Created by 博 on 2017/7/13.
*/
public class Page<T> {
private int totalCount ;
private int currentPage ;
private int totalPage ;
private int pageSize ;
private List<Ware> list ;
public int getTotalCount() {
return totalCount;
}
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
public int getTotalPage() {
return totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public List<Ware> getList() {
return list;
}
public void setList(List<Ware> list) {
this.list = list;
}
}
- 對分頁功能進行封裝,我們來先看一下代碼,我們再逐步分析。
package com.example.cne_shop.utils;
import android.content.Context;
import android.widget.Toast;
import com.cjj.MaterialRefreshLayout;
import com.cjj.MaterialRefreshListener;
import com.example.cne_shop.bean.Page;
import com.example.cne_shop.bean.Ware;
import com.example.cne_shop.okhttp.OkhttpHelper;
import com.example.cne_shop.okhttp.loadingSpotsDialog;
import com.squareup.okhttp.Response;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by 博 on 2017/7/15.
*/
public class Pager {
private List<Ware> mData ;
private static final int STATUS_NORMAL = 0 ;
private static final int STATUS_FRESH = 1 ;
private static final int STATUS_LOADMARE = 2 ;
private int status = STATUS_NORMAL ;
//定義一個靜態實例,接收數據,生成一個Pager實例對象
private static Pager.Builder builder ;
private OkhttpHelper okhttpHelper;
private Pager(){
okhttpHelper = OkhttpHelper.getOkhttpHelper() ;
getData() ;
setMaterialRefreshLayoutListener(builder.materialRefreshLayout) ;
}
public void changeParamsInUri(String key , Integer value){
builder.params.put(key , value+"") ;
}
public int getTotalCount (){
return this.builder.totalCount ;
}
//對MaterialRefreshLayout添加事件。實現上滑和下拉。
public void setMaterialRefreshLayoutListener (MaterialRefreshLayout materialRefreshLayout ){
materialRefreshLayout.setLoadMore(true);
materialRefreshLayout.setMaterialRefreshListener(new MaterialRefreshListener() {
@Override
public void onRefresh(MaterialRefreshLayout materialRefreshLayout) {
Toast.makeText(materialRefreshLayout.getContext() , "刷新" , Toast.LENGTH_SHORT).show();
freshData() ;
}
@Override
public void onRefreshLoadMore(MaterialRefreshLayout materialRefreshLayout) {
if( builder.curPage*builder.pageSize < builder.totalCount ){
Toast.makeText(materialRefreshLayout.getContext() , "加載更多" , Toast.LENGTH_SHORT).show();
loadMore() ;
}else {
materialRefreshLayout.finishRefreshLoadMore();
Toast.makeText(materialRefreshLayout.getContext() , "已經到底啦" , Toast.LENGTH_SHORT).show();
}
}
});
}
//實現刷新數據功能
public void freshData(){
status = STATUS_FRESH ;
builder.curPage = 1 ;
builder.putParams("curPage" , builder.curPage) ;
getData();
}
//實現加載更多功能
public void loadMore(){
status = STATUS_LOADMARE ;
builder.curPage = builder.curPage + 1 ;
builder.putParams("curPage" , builder.curPage) ;
getData();
}
//從服務器獲得數據
public void getData(){
okhttpHelper.doGet(builder.uri , new PageCallBack(builder.context) , builder.params);
}
//展示得到的數據
public <T> void showData(List<T> mData , int totalPage , int pageSize){
switch (status){
case STATUS_NORMAL :
if (builder.onPageListener !=null ){
builder.onPageListener.loadNormal(mData , totalPage ,pageSize);
}
break;
case STATUS_LOADMARE :
if (builder.onPageListener !=null ){
builder.onPageListener.loadMoreData(mData , totalPage ,pageSize);
}
builder.materialRefreshLayout.finishRefreshLoadMore();
status = STATUS_NORMAL ;
break;
case STATUS_FRESH :
if (builder.onPageListener !=null ){
builder.onPageListener.refData(mData , totalPage ,pageSize);
}
builder.materialRefreshLayout.finishRefresh();
status = STATUS_NORMAL ;
break;
}
}
public static Pager.Builder getBuilder(){
return builder = new Builder() ;
}
public interface OnPageListener<T>{
void loadNormal(List<T> mData , int totalPage , int pageSize) ;
void loadMoreData(List<T> mData , int totalPage , int pageSize);
void refData(List<T> mData , int totalPage , int pageSize) ;
}
/**
* 用來盛放所需要的所有數據。只涉及到數據的存取
*/
public static class Builder {
public int curPage = 1 ;
private int pageSize = 10 ;
public int totalCount = 1 ;
private String uri ;
private MaterialRefreshLayout materialRefreshLayout ;
private Type type ;
private Context context ;
private OnPageListener onPageListener ;
private List<Ware> mData ;
public Map<String, String> params ;
public Map<String, String> getParams() {
return params;
}
public Builder (){
params = new HashMap<>();
}
public Builder setOnPageListener(OnPageListener onPageListener){
builder.onPageListener = onPageListener ;
return builder;
}
public Builder setMaterialRefreshLayout(MaterialRefreshLayout materialRefreshLayout){
builder.materialRefreshLayout = materialRefreshLayout ;
return builder ;
}
public Builder setUri(String uri){
builder.uri = uri ;
return builder ;
}
public Pager build(Context context , Type type) throws Exception {
builder.type = type ;
builder.context = context ;
isVisible() ;
return new Pager() ;
}
public void isVisible() throws Exception {
if(builder.uri == null || builder.curPage==0 && builder.pageSize==0){
throw new Exception("uri 為空") ;
}
}
public Builder putParams(String Key , Object value){
builder.params.put(Key , value+"") ;
return builder ;
}
}
/**
* 繼承loadingSpotsDialog<Page<T>>實現對網絡加載攔截處理。
* @param <T>
*/
class PageCallBack<T> extends loadingSpotsDialog<Page<T>> {
public PageCallBack(Context context){
super(context);
this.type = builder.type ;
}
@Override
public void onErroe(Response response, int responseCode, Exception e) throws IOException {
this.closeSpotsDialog();
}
@Override
public void callBackSucces(Response response, Page<T> page) throws IOException {
builder.curPage = page.getCurrentPage() ;
builder.totalCount = page.getTotalCount() ;
showData(page.getList() , page.getTotalPage() , page.getPageSize());
this.closeSpotsDialog();
}
}
}
簡單陳述:
定義了一個靜態內部類Builder,Builder類用來盛放分頁刷新處理所需要的所有對象。
定義了一個內部類繼承了loadingSpotsDialog<Page<T>>實現了對網絡請求的攔截處理,在這部分完成將從服務器獲得的新的數據賦值給Builder,更新curPage , totalCount 。并將得到的商品信息展示到頁面。
定義了一個接口,將具體處理展示數據的過程交給調用者實現。
給MaterialRefreshLayout添加事件監聽。
上述為Page所做的事情。我們來大概理一遍執行順序,首先,調用靜態方法得到Builder類的對象,然后向對象添加curPage,pageSize, uri,MaterialRefreshLayout實例,OnPageListener接口實例等所需要的初始值,最后調用build方法。在build方法中調用了new page()方法,間接調用了setMaterialRefreshLayoutListener方法,對MaterialRefreshLayout添加監聽事件,一旦監聽到有上滑或者下拉動作時,將會立即執行相關方法,即更新uri(如果還有數據可供加載),調用getdata方法,通過okhttp對服務器發出請求,獲得新的數據,在我們新定義的類PageCallBack中處理了,請求結果。如果成功則調用showData()方法,對得到的數據進行展示。而showData()調用了,我們自定義接口的方法。這些方法需要調用者自行實現。
上面已經講述了封裝的page執行的大概過程,下面給出使用封裝的代碼
public void initMaterialRefreshLayout() throws Exception {
String uri = Contents.API.HOT ;
Pager.Builder builder = Pager.getBuilder()
.setMaterialRefreshLayout(materialRefreshLayout)
.putParams("curPage" , 1)
.putParams("pageSize" , 10)
.setUri(uri)
.setOnPageListener(new Pager.OnPageListener<Ware>() {
@Override
public void loadNormal(List<Ware> mData, int totalPage, int pageSize) {
mHotAdapter =new HotAdapter(mContext , mData ) ;
// setItemlistenler() ;
recyclerView.setAdapter(mHotAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(mContext));
recyclerView.addItemDecoration(new MyDivider());
}
@Override
public void loadMoreData(List<Ware> mData, int totalPage, int pageSize) {
mHotAdapter.addData(mData);
}
@Override
public void refData(List<Ware> mData, int totalPage, int pageSize) {
mHotAdapter.cleanData();
mHotAdapter.addData(mData);
recyclerView.scrollToPosition(0);
}
}) ;
pager = builder.build(mContext , Page.class) ;
}
我們的封裝已經結束,大家快去使用吧。