編輯:關於Android編程
絕大多數項目總都會有各種形形色色的列表界面,但其實這些界面的區別就在於列表子項的布局不同和點擊事件的響應不同而已,然而每次有個新的列表界面從適配器到Activity都需要寫很多代碼。作為一個懶人, 一直想把這些重復的工作給去掉,要做到這點,我們的控件需要有以下的功能
1支持下拉刷新
2支持適配器抽象
3支持切換沒有數據界面圖片和文字(一般都會有個圖片和提示文字)
4支持切換加載失敗界面,且點擊自動重試(有的項目是點擊按鈕,有的是直接點擊失敗界面)
5支持加載更多
如圖
vc+88rWlo6zO0sPH19S8usztvNOjrNPJ09q809TYuPy24MrH0OjSqrj5vt3B0LHtv9i8/sC0tcSjrNTdyrHO0sPHvs3KudPDTGlzdFZpZXejrLrzw+a74cK90PjM7bzTuPfW1rK7zay1xMTayN12aWV3oaM8YnIgLz4NCrzIyLvSqsjD1eK49r/YvP7KudPDvPK1paOsztLDx77N0qrX9rW91ruxqcK20OjSqsXk1sO1xLe9t6ihozxiciAvPg0KscjI57Wxx7DB0LHtyse38b/J0tTLotDCo6zKx7fx1qez1s/C1Ni4/Lbgo6zKp7DctcS958Pms6TKssO00fnX06Osw7vT0Mr9vt3M4cq+zsSxvrXItcihozxiciAvPg0Kz8LD5sz50ru2zs7S19S8urfi17C6w8HLtcS/2Lz+tcS199PDyrXA/aOsPGJyIC8+DQrB0LHtv9i8/rXExeTWwzwvcD4NCjxwcmUgY2xhc3M9"brush:java;">
mLayout.setAdapter(new SBAdapter
適配器的數據綁定
public void bindListData(Holder holder, BookCarInfo item) { holder.setText(R.id.model, item.getModel_name()); holder.setText(R.id.lable, MessageFormat.format(INFO_STR, DateUtil.formatDateToString(item.getReg_date(), "yyyy-MM-dd", "yyyy-MM"), item.getMile_age(), item.getCity_name())); ImageUtil.load(item.getPic(), holder.getView(R.id.img), ImageUtil.imageBuilder); holder.setText(R.id.time, DateUtil.formatDateToString(item.getUpdate_time(), DateUtil.dateFormatYMDHMS, "MM-dd") + " 發布"); if (item.getPrice().equals("0.00")) { holder.getView(R.id.price).setVisibility(View.GONE); holder.getView(R.id.price_lable).setVisibility(View.GONE); } else { holder.getView(R.id.price).setVisibility(View.VISIBLE); holder.getView(R.id.price_lable).setVisibility(View.VISIBLE); holder.setText(R.id.price, (int) Float.parseFloat(item.getPrice()) + ""); } holder.getView(R.id.layout).setOnClickListener(v -> EnquirydetailNuit.getCarDetail(item.getCar_id(), BookEnquiryListActivity.this)); }
網絡請求
public void loadData(boolean refresh) { if (refresh) { page = 1; } new HttpUtil.Builder("demo/api_enquiry_car/car_list") .Params("page", page + "") .Params(mQuery) .Success(m -> { BaseModel model1 = new BaseModel(m); ListcarList = (List ) JsonUtil.fromJson(model1.data, new TypeToken >() { }); if (carList != null) { if (carList.size() == 20) { mLayout.hasMoreData(true); } else { mLayout.hasMoreData(false); } page++; if (!refresh) { mLayout.loadMoreComplete(carList); } else { mLayout.refreshComplete(carList); } EventBus.getDefault().post(new CommentChangeEvent()); } else { mLayout.Failure(); } }) .Error(v -> mLayout.Failure()) .post(); }
整個列表界面的所有代碼都在裡面了,不再需要額外去寫一個適配器類,也不需要根據不同情況去顯示隱藏各種界面
下面我們來看怎麼封裝這樣的一個控件
首先我們需要定義幾個接口
控件觸發刷新動作的回調
@FunctionalInterface public interface RefreshListener { void onRefresh(); }
觸發加載更多的回調
@FunctionalInterface public interface LoadMoreListener { void LoadMore(); }
無數據的抽象類
public abstract class NoDataLayout extends RelativeLayout { public NoDataLayout(Context context) { this(context, null); } public NoDataLayout(Context context, AttributeSet attrs) { this(context, null, 0); } public NoDataLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public abstract NoDataLayout setLableText(String str); public abstract NoDataLayout setLable2Text(String str); public abstract NoDataLayout setImageDrawable(int res); }
這裡之所以要定義成抽象類是因為我的項目中無數據是的展示界面是不固定的,有兩種界面,而我又不想通過寫一個接口來來通知Activity來顯示隱藏不同的布局,所以我就把這個界面類給抽象了,通過實現這個抽象類來實現兩個不同的界面來在有需要更改無數據界面的時候給他替換掉。如果你的項目的列表界面的無數據界面是一直的,就不需要這樣。
** * 加載更多接口 * Created by 耿 on 2016/9/8. */ public interface FooterLayout { public void onReset(); public void onRefreshFailure(); public View getFooterView(); public View setAddLayoutParams(ViewGroup.LayoutParams params); public void onRefreshing(); public void onNoMoreData(); }
同樣的,加載更多的界面也是不同的,依賴於接口,當某個新的列表界面需要不同的加載更多的樣式的時候實現這個接口寫一個新布局就好。
接下來就是適配器了
我之前寫過一個RecyclerView的通用適配器
這裡由於是用的ListView,所以另寫了個通用的ListView適配器,這個網上有很多介紹。不過有一點不同的是由於我想在以後支持切換列表控件,所以我的列表控件中的適配器必須依賴於抽象
定義一個抽象接口
public interface BAdapter{ public List getList(); //初始添加數據 public void appendList(List list); //獲取適配器 public T getAdapter(); //刷新數據 public void notifyDataChanged(); //加載更多數據 public void addList(List list); }
實現這個接口,寫一個通用的ListView適配器
public class SBAdapterextends BaseAdapter implements BAdapter { private Context mContext; private List list; protected LayoutInflater mInflater; private int mItemLayoutId; Converter converter; public SBAdapter(Context context) { // TODO Auto-generated constructor stub this.mContext = context; this.mInflater = LayoutInflater.from(mContext); this.mItemLayoutId = new LinearLayout(mContext).getId(); this.list = new ArrayList<>(); } public SBAdapter(Context context, List list) { this.mContext = context; this.mInflater = LayoutInflater.from(mContext); this.mItemLayoutId = new LinearLayout(mContext).getId(); this.list = list; } public SBAdapter(Context context, List list, int itemLayoutId) { this.mContext = context; this.mInflater = LayoutInflater.from(mContext); this.mItemLayoutId = itemLayoutId; this.list = list; } public SBAdapter(Context context, int itemLayoutId) { this.mContext = context; this.mInflater = LayoutInflater.from(mContext); this.mItemLayoutId = itemLayoutId; this.list = new ArrayList(); } public SBAdapter list(List list) { this.list = list; return this; } public SBAdapter layout(int itemLayoutId) { this.mItemLayoutId = itemLayoutId; return this; } public SBAdapter bindViewData(Converter converter) { this.converter = converter; return this; } @Override public int getCount() { // TODO Auto-generated method stub return list.size(); } @Override public List getList() { // TODO Auto-generated method stub return this.list; } @Override public void appendList(List list) { // TODO Auto-generated method stub this.list = list; notifyDataSetChanged(); } @Override public BaseAdapter getAdapter() { return this; } @Override public void notifyDataChanged() { notifyDataSetChanged(); } @Override public void addList(List list2) { // TODO Auto-generated method stub this.list.addAll(list2); notifyDataSetChanged(); } @Override public T getItem(int position) { // TODO Auto-generated method stub return (T)this.list.get(position); } @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { final ViewHolder viewHolder = getViewHolder(position, convertView, parent); converter.convert(viewHolder, getItem(position)); return viewHolder.getConvertView(); } private ViewHolder getViewHolder(int position, View convertView, ViewGroup parent) { return ViewHolder.get(mContext, convertView, parent, mItemLayoutId, position); }
貼完整的類
public class RefreshLayout extends RelativeLayout { public final static int LISTMODE_RECYCLER = 0; public final static int LISTMODE_LISTVIEW_OR_GRIDVIEW = 1; SwipeRefreshLayout mSwipeRefreshLayout; ListView mListView; Context mContext; BAdapter adapter; RefreshListener mRefreshListener; LoadMoreListener mLoadMoreListener; AbsListView.OnScrollListener mScrollListener; View mFailureView; NoDataLayout mNoDataView; boolean isLoadMore = false; boolean hasMoreData = false; boolean loadMoreEnable = true; FooterLayout mLoadMoreView; public int LISTMODE = LISTMODE_LISTVIEW_OR_GRIDVIEW; String noDataLable = "", noDataLable2 = ""; int noDataImg = R.drawable.img_no_message; private void init() { mSwipeRefreshLayout = new SwipeRefreshLayout(mContext); addView(mSwipeRefreshLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); mListView = new ListView(mContext); mListView.setDivider(new BitmapDrawable()); mListView.setSelector(new BitmapDrawable()); mListView.setDividerHeight(0); mListView.setOverScrollMode(View.OVER_SCROLL_NEVER); mSwipeRefreshLayout.addView(mListView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); mSwipeRefreshLayout.setColorSchemeColors(0xffff2500); mSwipeRefreshLayout.setOnRefreshListener(() -> { isLoadMore = false; if (mRefreshListener != null) { mRefreshListener.onRefresh(); } } ); mFailureView = LayoutInflater.from(mContext).inflate(R.layout.bad_network, null); mFailureView.findViewById(R.id.reload).setOnClickListener((v) -> { mFailureView.setVisibility(View.GONE); mSwipeRefreshLayout.setRefreshing(true); mSwipeRefreshLayout.setProgressViewOffset(false, 0, DensityUtils.dp2px(mContext, 50)); if (mRefreshListener != null) { mRefreshListener.onRefresh(); } }); mNoDataView = new SimpleNoDataLayout(mContext); addView(mNoDataView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); mNoDataView.setVisibility(View.GONE); addView(mFailureView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); mLoadMoreView = new FooterLoadingLayout(mContext); AbsListView.LayoutParams params = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, DensityUtils.dp2px(mContext, 40)); mLoadMoreView.setAddLayoutParams(params); mListView.addFooterView(mLoadMoreView.getFooterView(), null, false); } public RefreshLayout refreshEnable(boolean enable) { mSwipeRefreshLayout.removeView(mListView); removeView(mSwipeRefreshLayout); removeView(mListView); if (enable) { addView(mSwipeRefreshLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); mSwipeRefreshLayout.addView(mListView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); } else { addView(mListView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); } return this; } public RefreshLayout ListMode(int mode) { this.LISTMODE = mode; return this; } /* *手動調用刷新 *@author 耿 *@date 2016/9/8 15:06 */ public void doRefresh() { if (mRefreshListener == null) { return; } mSwipeRefreshLayout.setProgressViewOffset(false, 0, DensityUtils.dp2px(mContext, 10)); mSwipeRefreshLayout.setRefreshing(true); if (mRefreshListener != null) { mRefreshListener.onRefresh(); } } //刷新完成添加列表數據 public void refreshComplete(List list) { adapter.appendList(list); mSwipeRefreshLayout.setRefreshing(false); mNoDataView.setVisibility(View.GONE); if (list.size() == 0) { if (loadMoreEnable) { mLoadMoreView.onReset(); } if (mNoDataView != null) mNoDataView.setVisibility(View.VISIBLE); } } //設置無數據時界面 public RefreshLayout noDataView(NoDataLayout view) { removeView(mNoDataView); mNoDataView = view; mNoDataView.setLableText(noDataLable); mNoDataView.setLable2Text(noDataLable2); mNoDataView.setImageDrawable(noDataImg); addView(mNoDataView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); mNoDataView.setVisibility(View.GONE); return this; } //設置無數據時提示文本 public RefreshLayout noDataLable(String str) { noDataLable = str; mNoDataView.setLableText(str); return this; } //設置無數據時第二行提示文本 public RefreshLayout noDataLable2(String str) { noDataLable2 = str; mNoDataView.setLable2Text(str); return this; } //設置無數據時圖片 public RefreshLayout noDataImg(int resId) { noDataImg = resId; mNoDataView.setImageDrawable(resId); return this; } //加載更多完成後添加數據 public void loadMoreComplete(List list) { adapter.addList(list); isLoadMore = false; } //數據請求失敗調用 public void Failure() { mSwipeRefreshLayout.setRefreshing(false); if (!isLoadMore) { adapter.getList().clear(); adapter.notifyDataChanged(); mFailureView.findViewById(R.id.bad_network).setVisibility(View.VISIBLE); } else { hasMoreData = false; isLoadMore = false; mLoadMoreView.onRefreshFailure(); } } //添加自定義沒有數據時的界面 public RefreshLayout addNoDataView(NoDataLayout v) { mNoDataView = v; return this; } //添加自定義加載更多界面 public RefreshLayout FooterLayout(FooterLayout layout) { mLoadMoreView = layout; return this; } //是否允許控件加載更多 public RefreshLayout loadMoreEnable(boolean b) { loadMoreEnable = b; if (!loadMoreEnable) { mListView.removeFooterView(mLoadMoreView.getFooterView()); } return this; } public BAdapter getAdapter() { return this.adapter; } public List getList() { return this.adapter.getList(); } //某次請求後。是否有更多數據需要加載 public void hasMoreData(boolean b) { if (!loadMoreEnable) return; hasMoreData = b; isLoadMore = false; mLoadMoreView.onReset(); if (!hasMoreData) { mLoadMoreView.onNoMoreData(); } else { mListView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView absListView, int newState) { if (loadMoreEnable && hasMoreData && !isLoadMore) { if (newState == ListView.OnScrollListener.SCROLL_STATE_IDLE || newState == ListView.OnScrollListener.SCROLL_STATE_FLING) { if (isLastItemVisible()) { if (mLoadMoreListener != null) { mLoadMoreListener.LoadMore(); mLoadMoreView.onRefreshing(); isLoadMore = true; } } } } if (null != mScrollListener) { mScrollListener.onScrollStateChanged(absListView, newState); } } @Override public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (null != mScrollListener) { mScrollListener.onScroll(absListView, firstVisibleItem, visibleItemCount, totalItemCount); } } }); } } private boolean isLastItemVisible() { final Adapter adapter = mListView.getAdapter(); if (null == adapter || adapter.isEmpty()) { return true; } final int lastItemPosition = adapter.getCount() - 1; final int lastVisiblePosition = mListView.getLastVisiblePosition(); /** * This check should really just be: lastVisiblePosition == lastItemPosition, but ListView * internally uses a FooterView which messes the positions up. For me we'll just subtract * one to account for it and rely on the inner condition which checks getBottom(). */ if (lastVisiblePosition >= lastItemPosition - 1) { final int childIndex = lastVisiblePosition - mListView.getFirstVisiblePosition(); final int childCount = mListView.getChildCount(); final int index = Math.min(childIndex, childCount - 1); final View lastVisibleChild = mListView.getChildAt(index); if (lastVisibleChild != null) { return lastVisibleChild.getBottom() <= mListView.getBottom(); } } return false; } //下拉刷新回調 public RefreshLayout Refresh(RefreshListener refreshListener) { mRefreshListener = refreshListener; return this; } //添加listview滾動監聽 public RefreshLayout OnListViewScrollListener(AbsListView.OnScrollListener scrollListener) { mScrollListener = scrollListener; return this; } //加載更多回調 public RefreshLayout LoadMore(LoadMoreListener loadMoreListener) { mLoadMoreListener = loadMoreListener; return this; } //設置適配器 public RefreshLayout setAdapter(BAdapter adapter) { this.adapter = adapter; mListView.setAdapter((BaseAdapter) adapter.getAdapter()); return this; } public RefreshLayout(Context context) { this(context, null); } public RefreshLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; init(); } }
這個類直接拷貝是不可以運行的,因為裡面有幾個實現類我沒有貼,主要拷貝過去也不會完全適合你的項目,所以看看思路就好,相信大家可以根據 這個思路寫出適合自己項目的列表控件的
實在想要現成的源碼的可以私聊我
我建了一個QQ群(群號:121606151),用於大家討論交流Android技術問題,有興趣的可以加下,大家一起進步。
這個是一個以弧線為依托的進度控件,主要包括了兩個圓弧、一個圓、一個文本。 當我們點擊開始按鈕的時候,會出現一個動畫,逐漸的出現進度,好了,下面開始我們的編碼。新
為了使得一個程序能夠在同一時間裡處理許多用戶的要求。即使用戶可能發出一個要求,也肯能導致一個操作系統中多個進程的運行(PS:聽音樂,看地圖)。而且多個進程間需要相互交換、
環境搭建git clone cd 到AndBug目錄,執行make58deMacBook-Pro-7:AndBug wuxian$ makePYTHONPATH=lib
在《Qt on Android: Qt Quick 事件處理之信號與槽》一文中介紹自定義信號時,舉了一個簡單的例子,定義了一個顏色選擇組件,當用戶在組建內點擊鼠標時,該組