(尊重原创转载请说明来处,谢谢)
android自定义控件,就是根据自己的喜好、项目需求随意设计制作控件的外表和功能,不可谓不霸气!之前写了一篇关于带有编辑和删除功能的侧滑ListView,感觉效果不错的样子,所以现在再来写一篇最近实现的一个控件:带有下拉刷新和选择框的ListView,咱也是有点审美的人是不是,那还是老套路大家先看脸呗,毕竟相貌过得去才有干劲嘛~~~
个人呢觉得程序嘛,重要的是思路,只要有了思路,问题肯定是可以解决的;反过来说,如果只注重细枝末节而忽略了总体思想,效果很可能是不理想的,印象也不是最深刻的。那么问题来了,我为什么要说这些呢?因为下面我会主要讲解自己的思路,最后还会附上亲测可以运行的代码供大家学习,欢迎各位大神拍砖:
1:先说一下选择框:大家一定会认为我在item的布局中放置了一个checkbox是不是?我确实是这么做的,但是呢,checkbox的优先级是高于listview的,所以为了解决冲突,在checkbox的布局文件中添加了这些属性,
android:focusable="false" android:focusableInTouchMode="false" android:clickable="false"此外在adapter中使用了HashMap<Integer,Boolean>来记录checkbox的选中状态,点击一次选中,再次点击时取消
item代码:
<?xml version="1.0" encoding="utf-8"?> <!-- ListView的头部 --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <!-- 内容 --> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/head_contentLayout" > <!-- 箭头头像、进度条 --> <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_marginLeft="50dp" android:layout_centerVertical="true"> <!-- 箭头 --> <ImageView android:id="@+id/head_arrowImageView" android:layout_width="50dp" android:layout_height="50dp" android:layout_gravity="center" android:src="@drawable/upward"/> <!-- 进度条 --> <ProgressBar android:id="@+id/head_progressBar" android:layout_width="40dp" android:layout_height="40dp" style="?android:attr/progressBarStyleSmall" android:layout_gravity="center" android:visibility="gone"/> </FrameLayout> <!-- 提示、最近更新 --> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:orientation="vertical" android:gravity="center_horizontal"> <!-- 提示 --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:layout_marginBottom="20dp" android:textColor="@android:color/white" android:id="@+id/head_tipsTextView" android:textSize="20sp" android:text="head_tips_text"/> </LinearLayout> </RelativeLayout> </LinearLayout>
2:下拉刷新问题:首先呢是自定义了一个title布局文件,布局文件里有TextView、Img什么的,而这个布局文件是依靠代码将它添加到listview中,也就是说listview多了一项,只不过这个item平时是隐藏的,当手指滑动时,代码控制它动态显示或者隐藏,headTitle布局代码:
<?xml version="1.0" encoding="utf-8"?> <!-- ListView的头部 --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <!-- 内容 --> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/head_contentLayout" > <!-- 箭头头像、进度条 --> <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_marginLeft="50dp" android:layout_centerVertical="true"> <!-- 箭头 --> <ImageView android:id="@+id/head_arrowImageView" android:layout_width="50dp" android:layout_height="50dp" android:layout_gravity="center" android:src="@drawable/upward"/> <!-- 进度条 --> <ProgressBar android:id="@+id/head_progressBar" android:layout_width="40dp" android:layout_height="40dp" style="?android:attr/progressBarStyleSmall" android:layout_gravity="center" android:visibility="gone"/> </FrameLayout> <!-- 提示、最近更新 --> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:orientation="vertical" android:gravity="center_horizontal"> <!-- 提示 --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:layout_marginBottom="20dp" android:textColor="@android:color/white" android:id="@+id/head_tipsTextView" android:textSize="20sp" android:text="head_tips_text"/> </LinearLayout> </RelativeLayout> </LinearLayout>
package com.lei.diyrefresh; import android.widget.ListView; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListAdapter; import android.widget.ProgressBar; import android.widget.TextView; public class RefreshCheckBoxListView extends ListView implements OnScrollListener { private final static int RELEASE_TO_REFRESH = 0; private final static int PULL_TO_REFRESH = 1; //正在刷新 private final static int REFRESHING = 2; //刷新完成 private final static int DONE = 3; private final static int LOADING = 4; private final static int RADIO = 3; private LayoutInflater mInflater; private LinearLayout mHeadView;//header private TextView mTipsTextView;//header 标题 private ImageView mArrowImageView;//动画图片 private ProgressBar mProgressBar; private RotateAnimation mAnimation;//逆时针180 private RotateAnimation mReverseAnimation;//顺时针180 private boolean mIsRecored; private int mHeadContentWidth; private int mHeadContentHeight; private int mStartY; private int mFirstItemIndex; private int mState; private boolean mIsBack; private boolean mISRefreshable; private OnRefreshListener mRefreshListener; public RefreshCheckBoxListView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } private void initView(Context context) { mInflater = LayoutInflater.from(context); mHeadView = (LinearLayout) mInflater.inflate(R.layout.item_head_of_listview, null); mArrowImageView = (ImageView) mHeadView.findViewById(R.id.head_arrowImageView); mProgressBar = (ProgressBar) mHeadView.findViewById(R.id.head_progressBar); mTipsTextView = (TextView) mHeadView.findViewById(R.id.head_tipsTextView); measureView(mHeadView);//测量 mHeadContentHeight = mHeadView.getMeasuredHeight(); System.out.println("mHeadContentHeight = " + mHeadContentHeight); mHeadContentWidth = mHeadView.getMeasuredWidth(); System.out.println("mHeadContentWidth = " + mHeadContentWidth); mHeadView.setPadding(0, -1 * mHeadContentHeight, 0, 0); mHeadView.invalidate(); addHeaderView(mHeadView, null, false);//将header添加到listView中 setOnScrollListener(this); mAnimation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); mAnimation.setInterpolator(new LinearInterpolator()); mAnimation.setDuration(250); mAnimation.setFillAfter(true); mReverseAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); mReverseAnimation.setInterpolator(new LinearInterpolator()); mReverseAnimation.setDuration(250); mReverseAnimation.setFillAfter(true); mState = DONE; mISRefreshable = false; } private void measureView(View child) { android.view.ViewGroup.LayoutParams params = child.getLayoutParams(); System.out.println("params = " + params); if(params == null) { params = new LayoutParams(android.view.ViewGroup.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); } System.out.println("lpWidth = " + params.width); int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0+0, params.width); System.out.println("childWidthSpec = " + childWidthSpec); int lpHeight = params.height; System.out.println("lpHeight = " + lpHeight); int childHeightSpec; if(lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.UNSPECIFIED); } System.out.println("childHeightSpec = " + childHeightSpec); child.measure(childWidthSpec, childHeightSpec); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { mFirstItemIndex = firstVisibleItem; } public interface OnRefreshListener { void onRefresh(); } private void onRefresh() { if(mRefreshListener != null) { mRefreshListener.onRefresh(); } } public void onRefreshComplete() { mState = DONE; changeHeaderViewByState(); } public void setonRefreshListener(OnRefreshListener onRefreshListener) { this.mRefreshListener = onRefreshListener; mISRefreshable = true; } @Override public boolean onTouchEvent(MotionEvent ev) { if(mISRefreshable) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: if(mFirstItemIndex == 0 && !mIsRecored) { mIsRecored = true; mStartY = (int) ev.getY(); } break; case MotionEvent.ACTION_UP: if(mState != REFRESHING && mState != LOADING) { if(mState == DONE) { } if(mState == PULL_TO_REFRESH) { mState = DONE; changeHeaderViewByState(); } if(mState == RELEASE_TO_REFRESH) { mState = REFRESHING; changeHeaderViewByState(); onRefresh(); } } mIsBack = false; mIsRecored = false; break; case MotionEvent.ACTION_MOVE: int tempY = (int) ev.getY(); if(!mIsRecored && mFirstItemIndex == 0) { mIsRecored = true; mStartY = tempY; } if(mState != REFRESHING && mIsRecored && mState != LOADING) { if(mState == RELEASE_TO_REFRESH) { setSelection(0); if((tempY - mStartY)/RADIO < mHeadContentHeight && (tempY - mStartY) > 0) { mState = PULL_TO_REFRESH; changeHeaderViewByState(); } else if(tempY - mStartY <= 0) { mState = DONE; changeHeaderViewByState(); } } if(mState == PULL_TO_REFRESH) { //下拉长度达到header的3倍高度时,进行释放刷新提示 setSelection(0); if((tempY - mStartY)/RADIO >= mHeadContentHeight) { mState = RELEASE_TO_REFRESH; mIsBack = true; changeHeaderViewByState(); } } else if(tempY - mStartY <= 0) { //上划情况 mState = DONE; changeHeaderViewByState(); } if(mState == DONE) {//如果下拉重置state状态 if(tempY - mStartY > 0) { mState = PULL_TO_REFRESH; changeHeaderViewByState(); } } if(mState == PULL_TO_REFRESH) {//释放刷新 mHeadView.setPadding(0, -1 * mHeadContentHeight + (tempY - mStartY)/RADIO, 0, 0); } if(mState == RELEASE_TO_REFRESH) { mHeadView.setPadding(0, (tempY - mStartY)/RADIO - mHeadContentHeight, 0, 0); } } break; default: break; } } return super.onTouchEvent(ev); } private void changeHeaderViewByState() {//control the state of header switch (mState) { case PULL_TO_REFRESH: mProgressBar.setVisibility(GONE); mTipsTextView.setVisibility(VISIBLE); mArrowImageView.clearAnimation(); mArrowImageView.setVisibility(VISIBLE); if(mIsBack) { mIsBack = false; mArrowImageView.clearAnimation(); mArrowImageView.startAnimation(mReverseAnimation); mTipsTextView.setText("isBack is true!!!"); } else { mTipsTextView.setText("isBack is false!!!"); } break; case DONE: mHeadView.setPadding(0, -1 * mHeadContentHeight, 0, 0); mProgressBar.setVisibility(GONE); mArrowImageView.clearAnimation(); mArrowImageView.setImageResource(R.drawable.upward); mTipsTextView.setText("加载完毕"); break; case REFRESHING: mHeadView.setPadding(0, 0, 0, 0); mProgressBar.setVisibility(VISIBLE); mArrowImageView.clearAnimation(); mArrowImageView.setVisibility(GONE); mTipsTextView.setText("努力加载中……"); break; case RELEASE_TO_REFRESH: mArrowImageView.setVisibility(VISIBLE); mProgressBar.setVisibility(GONE); mTipsTextView.setVisibility(VISIBLE); mArrowImageView.clearAnimation(); mArrowImageView.startAnimation(mAnimation); mTipsTextView.setText("释放刷新"); break; default: break; } } @Override public void setAdapter(ListAdapter adapter) { super.setAdapter(adapter); } }
package com.lei.diyrefresh; import android.content.Context; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.CheckBox; import android.widget.TextView; import java.util.HashMap; import java.util.List; import java.util.Map; public class RefreshListViewAdapter extends BaseAdapter { private static String TAG="RefreshListViewAdapter"; private Context context; private List<String> stringList; public static Map<Integer, Boolean> isSelected; public RefreshListViewAdapter(Context context, List<String> list){ this.context = context; this.stringList = list; setStart(); } private void setStart(){ isSelected = new HashMap<Integer, Boolean>(); for (int i = 0; i < stringList.size(); i++) { isSelected.put(i, false); } } @Override public int getCount() { return stringList.size(); } @Override public Object getItem(int position) { return stringList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { String bulb=stringList.get(position); View view; ViewHolder viewHolder; if (convertView==null){ view= LayoutInflater.from(context).inflate(R.layout.item_pull_to_refresh_with_checkbox,null); viewHolder=new ViewHolder(); viewHolder.tvName=(TextView)view.findViewById(R.id.tvRouterName); viewHolder.tvContent=(TextView)view.findViewById(R.id.tvContent); viewHolder.cBox=(CheckBox)view.findViewById(R.id.cBox); view.setTag(viewHolder);//store up viewHolder }else { view=convertView; viewHolder=(ViewHolder)view.getTag(); } viewHolder.tvContent.setText("position: " + position); viewHolder.tvName.setText(bulb); viewHolder.cBox.setChecked(isSelected.get(position));//isSelected.get(position) Log.i(TAG, "position: " + position + " " + isSelected.get(position)); /* if ((position%2)==1){ viewHolder.cBox.setChecked(true);//isSelected.get(position) Log.i(TAG, "单数 " + position); }else { viewHolder.cBox.setChecked(false);//isSelected.get(position) Log.i(TAG, "偶数 "+position); } */ return view; } public class ViewHolder{ TextView tvName,tvContent; public CheckBox cBox; } }
package com.lei.diyrefresh; import android.os.AsyncTask; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private static String TAG="MainActivity"; RefreshCheckBoxListView mRefreshCheckBoxListView; RefreshListViewAdapter listViewAdapter; List<String> list=new ArrayList<String>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getData(); initView(); } private void initView(){ mRefreshCheckBoxListView=(RefreshCheckBoxListView)findViewById(R.id.lvPull); listViewAdapter=new RefreshListViewAdapter(this,list); mRefreshCheckBoxListView.setAdapter(listViewAdapter); mRefreshCheckBoxListView.setItemsCanFocus(false); mRefreshCheckBoxListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); mRefreshCheckBoxListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { RefreshListViewAdapter.ViewHolder vHolder = (RefreshListViewAdapter.ViewHolder) view.getTag(); //在每次获取点击的item时将对应的checkbox状态改变,同时修改map的值。 vHolder.cBox.toggle(); RefreshListViewAdapter.isSelected.put(position - 1, vHolder.cBox.isChecked()); //Log.i(TAG, "position: " + position); } }); mRefreshCheckBoxListView.setonRefreshListener(new RefreshCheckBoxListView.OnRefreshListener() { @Override public void onRefresh() { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return null; } protected void onPostExecute(Void result) { listViewAdapter.notifyDataSetChanged(); mRefreshCheckBoxListView.onRefreshComplete(); } }.execute(); } }); } private void getData(){ for (int i=0;i<20;i++){ list.add(new String("第 "+i)); } } }注意事项:因为之前代码添加head到listview中,导致listview增加了一项,所以在这里position要减1
RefreshListViewAdapter.isSelected.put(position - 1, vHolder.cBox.isChecked());
主布局文件:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.lei.diyrefresh.MainActivity"> <com.lei.diyrefresh.RefreshCheckBoxListView android:id="@+id/lvPull" android:layout_width="match_parent" android:layout_height="wrap_content"> </com.lei.diyrefresh.RefreshCheckBoxListView> </RelativeLayout>