下拉刷新、加载更多、标记已读、轮播条、缓存
下拉刷新-------
1.addHeaderView必须在setAdapter之前调用
2.将paddingTop设置一个headerView高度的负值去隐藏它
getHeight()和getMeasuredHeight()的区别:
getMeasuredHeight():获取测量完的高度,只要在onMeasure方法执行完,就可以用
它获取到宽高,在自定义控件内部多使用这个
使用view.measure(0,0)方法可以主动通知系统去测量,然后就
可以直接使用它获取宽高
getHeight():必须在onLayout方法执行完后,才能获得宽高
view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
headerView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int headerViewHeight = headerView.getHeight();
//直接可以获取宽高
}
});
3.setSelection(position);将对应位置的item放置到屏幕顶端
下拉刷新的原理:
1.自定义带下拉刷新,加载更多的listview
代码语言:javascript复制/*
* 提示:该行代码过长,系统自动注释不进行高亮。一键复制会移除系统注释
* public class RefreshListView extends ListView implements OnScrollListener, android.widget.AdapterView.OnItemClickListener { private static final int STATE_PULL_REFRESH = 0;// 下拉刷新 private static final int STATE_RELEASE_REFRESH = 1;// 松开刷新 private static final int STATE_REFRESHING = 2;// 正在刷新 private View mHeaderView; private int startY = -1;// 滑动起点的y坐标 private int mHeaderViewHeight; private int mCurrrentState = STATE_PULL_REFRESH;// 当前状态 private TextView tvTitle; private TextView tvTime; private ImageView ivArrow; private ProgressBar pbProgress; private RotateAnimation animUp; private RotateAnimation animDown; public RefreshListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initHeaderView(); initFooterView(); } public RefreshListView(Context context, AttributeSet attrs) { super(context, attrs); initHeaderView(); initFooterView(); } public RefreshListView(Context context) { super(context); initHeaderView(); initFooterView(); } /** * 初始化头布局 */ private void initHeaderView() { mHeaderView = View.inflate(getContext(), R.layout.refresh_header, null); this.addHeaderView(mHeaderView); tvTitle = (TextView) mHeaderView.findViewById(R.id.tv_title); tvTime = (TextView) mHeaderView.findViewById(R.id.tv_time); ivArrow = (ImageView) mHeaderView.findViewById(R.id.iv_arr); pbProgress = (ProgressBar) mHeaderView.findViewById(R.id.pb_progress); mHeaderView.measure(0, 0);//测量之后该view的getMeasuredHeight()就会返回刚才测量所得的高,getMeasuredWidth返回测量所得宽 mHeaderViewHeight = mHeaderView.getMeasuredHeight(); mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);// 隐藏头布局 initArrowAnim(); tvTime.setText("最后刷新时间:" getCurrentTime()); } /* * 初始化脚布局 */ private void initFooterView() { mFooterView = View.inflate(getContext(), R.layout.refresh_listview_footer, null); this.addFooterView(mFooterView); mFooterView.measure(0, 0); mFooterViewHeight = mFooterView.getMeasuredHeight(); mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);// 隐藏 this.setOnScrollListener(this);//加载更多需要实现OnScrollListener接口 } @Override public boolean onTouchEvent(MotionEvent ev) {//下拉刷新需要重写这个方法:触摸事件 switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: startY = (int) ev.getRawY(); break; case MotionEvent.ACTION_MOVE: if (startY == -1) {// 确保startY有效,有时候ACTION_DOWN事件不会调用,需要重新掉一次 startY = (int) ev.getRawY(); } if (mCurrrentState == STATE_REFRESHING) {// 正在刷新时不做处理 break; } int endY = (int) ev.getRawY(); int dy = endY - startY;// 移动便宜量 if (dy > 0 && getFirstVisiblePosition() == 0) {// 只有下拉并且当前是第一个item,才允许下拉 int padding = dy - mHeaderViewHeight;// 计算padding mHeaderView.setPadding(0, padding, 0, 0);// 设置当前padding if (padding > 0 && mCurrrentState != STATE_RELEASE_REFRESH) {// 状态改为松开刷新,头布局全部出来了 mCurrrentState = STATE_RELEASE_REFRESH; refreshState(); } else if (padding < 0 && mCurrrentState != STATE_PULL_REFRESH) {// 改为下拉刷新状态 mCurrrentState = STATE_PULL_REFRESH; refreshState(); } return true; } break; case MotionEvent.ACTION_UP: startY = -1;// 重置 if (mCurrrentState == STATE_RELEASE_REFRESH) { mCurrrentState = STATE_REFRESHING;// 正在刷新 mHeaderView.setPadding(0, 0, 0, 0);// 显示 refreshState(); } else if (mCurrrentState == STATE_PULL_REFRESH) { mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);// 隐藏 } break; default: break; } return super.onTouchEvent(ev); } /** * 刷新下拉控件的布局 */ private void refreshState() { switch (mCurrrentState) { case STATE_PULL_REFRESH: tvTitle.setText("下拉刷新"); ivArrow.setVisibility(View.VISIBLE); pbProgress.setVisibility(View.INVISIBLE); ivArrow.startAnimation(animDown);//箭头向下 break; case STATE_RELEASE_REFRESH: tvTitle.setText("松开刷新"); ivArrow.setVisibility(View.VISIBLE); pbProgress.setVisibility(View.INVISIBLE); ivArrow.startAnimation(animUp); break; case STATE_REFRESHING: tvTitle.setText("正在刷新..."); ivArrow.clearAnimation();// 必须先清除动画,才能隐藏 ivArrow.setVisibility(View.INVISIBLE); pbProgress.setVisibility(View.VISIBLE); if (mListener != null) { mListener.onRefresh();//不等于空时才刷新数据 } break; default: break; } } /** * 初始化箭头动画 */ private void initArrowAnim() { // 箭头向上动画 animUp = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); animUp.setDuration(200); animUp.setFillAfter(true); // 箭头向下动画 animDown = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); animDown.setDuration(200); animDown.setFillAfter(true); } OnRefreshListener mListener; private View mFooterView; private int mFooterViewHeight; public void setOnRefreshListener(OnRefreshListener listener) { mListener = listener; } public interface OnRefreshListener {//定义个接口 public void onRefresh(); public void onLoadMore();// 加载下一页数据 } /* * 收起下拉刷新的控件 */ public void onRefreshComplete(boolean success) {//加了个参数,成功才更新时间 if (isLoadingMore) {// 正在加载更多...如果是加载下一页 mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);// 隐藏脚布局, isLoadingMore = false; } else { mCurrrentState = STATE_PULL_REFRESH; tvTitle.setText("下拉刷新"); ivArrow.setVisibility(View.VISIBLE); pbProgress.setVisibility(View.INVISIBLE); mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);// 隐藏 if (success) { tvTime.setText("最后刷新时间:" getCurrentTime()); } } } /** * 获取当前时间 */ public String getCurrentTime() { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return format.format(new Date()); }//定义个标记,要不然一直掉这个到底了,判断现在是否加载了 private boolean isLoadingMore; @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == SCROLL_STATE_IDLE || scrollState == SCROLL_STATE_FLING) {//空闲和快速滑状态 if (getLastVisiblePosition() == getCount() - 1 && !isLoadingMore) {// 滑动到最后,默认是FALSE,然后置为TRUE System.out.println("到底了....."); mFooterView.setPadding(0, 0, 0, 0);// 显示 setSelection(getCount() - 1);// 改变listview显示位置 isLoadingMore = true;//致为TRUE,下次进来不调用了,可是如果还有数据,什么时候致为FALSE了?在onRefreshComplete方法里,加载结束后 if (mListener != null) { mListener.onLoadMore();// 在这里调用这个方法:加载下一页数据 } } } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } OnItemClickListener mItemClickListener; @Override public void setOnItemClickListener(//重写listview的setOnItemClickListener方法 android.widget.AdapterView.OnItemClickListener listener) { super.setOnItemClickListener(this);//改成this,并且实现OnItemClickListener 接口 mItemClickListener = listener; } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (mItemClickListener != null) {//这样就调用地方的点击条目就变成了减去头view后重新开始的position了 mItemClickListener.onItemClick(parent, view, position - getHeaderViewsCount(), id); } }}
*/
2.头布局
代码语言:javascript复制<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="10dp" > <ImageView android:id="@ id/iv_arr" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/common_listview_headview_red_arrow" /> <ProgressBar android:id="@ id/pb_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:indeterminateDrawable="@drawable/custom_progress" android:visibility="invisible" /> </FrameLayout> <LinearLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center" android:gravity="center" android:orientation="vertical" > <TextView android:id="@ id/tv_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="下拉刷新" android:textColor="#f00" android:textSize="16sp" /> <TextView android:id="@ id/tv_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:text="最后刷新时间:2015-03-10 17:07:07" android:textColor="@android:color/darker_gray" android:textSize="14sp" /> </LinearLayout></LinearLayout>
custom_progress:改变ProgressBar的样子,这里在drawable里自定义了一个圆圈
代码语言:javascript复制<rotate xmlns:android="http://schemas.android.com/apk/res/android" android:fromDegrees="0" android:pivotX="50%" android:pivotY="50%" android:toDegrees="360" > <shape android:innerRadius="12dp"//圈内半径 android:shape="ring"//圆环 android:thickness="3dp"//圆圈厚度 android:useLevel="false" > <gradient android:centerColor="#3f00"//开始,粉色,3是透明度 android:endColor="#f00"//中间 android:startColor="#fff" />//结束,循环转圈 </shape></rotate>
3.脚布局
代码语言:javascript复制<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal" > <ProgressBar android:id="@ id/pb_pull_list_header" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_margin="5dp" android:indeterminateDrawable="@drawable/custom_progress" /> <TextView android:id="@ id/tv_pull_list_header_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="加载中..." android:textColor="#ff0000" android:textSize="18sp" /></LinearLayout>
4.在页签详情页中调用自定义listview的方法
代码语言:javascript复制public class TabDetailPager extends BaseMenuDetailPager implements OnPageChangeListener { 。。。 private String mMoreUrl;// 更多页面的地址,数据里有的话才加载下一页
@Override public View initViews() { View view = View.inflate(mActivity, R.layout.tab_detail_pager, null); // 加载头布局 View headerView = View.inflate(mActivity, R.layout.list_header_topnews, null); ViewUtils.inject(this, view); ViewUtils.inject(this, headerView); // 将头条新闻以头布局的形式加给listview lvList.addHeaderView(headerView); // 设置下拉刷新监听 lvList.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh() { getDataFromServer(); } @Override public void onLoadMore() { if (mMoreUrl != null) { getMoreDataFromServer();//调用more接口 } else { Toast.makeText(mActivity, "最后一页了", Toast.LENGTH_SHORT) .show();// 收起加载更多的布局,传false和TRUE一样,只不过是是否更新更新时间的textview
lvList.onRefreshComplete(false); } } }); lvList.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { System.out.println("被点击:" position); // 35311,34221,34234,34342 // 在本地记录已读状态 String ids = PrefUtils.getString(mActivity, "read_ids", ""); String readId = mNewsList.get(position).id; if (!ids.contains(readId)) { ids = ids readId ","; PrefUtils.setString(mActivity, "read_ids", ids); } // mNewsAdapter.notifyDataSetChanged();//这样写也可以,不过重新调用getview方法// 实现局部界面刷新, 这个view就是被点击的item布局对象,注意这是个好方法
changeReadState(view); // 跳转新闻详情页 Intent intent = new Intent(); intent.setClass(mActivity, NewsDetailActivity.class); intent.putExtra("url", mNewsList.get(position).url); mActivity.startActivity(intent); } }); return view; } /** * 改变已读新闻的颜色 */ private void changeReadState(View view) { TextView tvTitle = (TextView) view.findViewById(R.id.tv_title); tvTitle.setTextColor(Color.GRAY); } @Override public void initData() { String cache = CacheUtils.getCache(mUrl, mActivity);//读取缓存 if (!TextUtils.isEmpty(cache)) { parseData(cache, false); } getDataFromServer();//不管用没有缓存都请求下服务器,看有没有最新的 } private void getDataFromServer() { HttpUtils utils = new HttpUtils(); utils.send(HttpMethod.GET, mUrl, new RequestCallBack<String>() { @Override public void onSuccess(ResponseInfo<String> responseInfo) { String result = (String) responseInfo.result; System.out.println("页签详情页返回结果:" result); parseData(result, false); lvList.onRefreshComplete(true); // 设置缓存 CacheUtils.setCache(mUrl, result, mActivity); } @Override public void onFailure(HttpException error, String msg) { Toast.makeText(mActivity, msg, Toast.LENGTH_SHORT).show(); error.printStackTrace(); lvList.onRefreshComplete(false) } }); } /** * 加载下一页数据 */ private void getMoreDataFromServer() { HttpUtils utils = new HttpUtils(); utils.send(HttpMethod.GET, mMoreUrl, new RequestCallBack<String>() { @Override public void onSuccess(ResponseInfo<String> responseInfo) { String result = (String) responseInfo.result; parseData(result, true); lvList.onRefreshComplete(true); } @Override public void onFailure(HttpException error, String msg) { Toast.makeText(mActivity, msg, Toast.LENGTH_SHORT).show(); error.printStackTrace(); lvList.onRefreshComplete(false); } }); } protected void parseData(String result, boolean isMore) { Gson gson = new Gson(); mTabDetailData = gson.fromJson(result, TabData.class); System.out.println("页签详情解析:" mTabDetailData); // 处理下一页链接 String more = mTabDetailData.data.more; if (!TextUtils.isEmpty(more)) { mMoreUrl = GlobalContants.SERVER_URL more; } else { mMoreUrl = null; } if (!isMore) { mTopNewsList = mTabDetailData.data.topnews; mNewsList = mTabDetailData.data.news; if (mTopNewsList != null) { mViewPager.setAdapter(new TopNewsAdapter()); mIndicator.setViewPager(mViewPager); mIndicator.setSnap(true);// 支持快照显示 mIndicator.setOnPageChangeListener(this); mIndicator.onPageSelected(0);// 让指示器重新定位到第一个点 tvTitle.setText(mTopNewsList.get(0).title); } if (mNewsList != null) { mNewsAdapter = new NewsAdapter(); lvList.setAdapter(mNewsAdapter); } // 自动轮播条显示 if (mHandler == null) { mHandler = new Handler() { public void handleMessage(android.os.Message msg) { int currentItem = mViewPager.getCurrentItem(); if (currentItem < mTopNewsList.size() - 1) { currentItem ; } else { currentItem = 0; } mViewPager.setCurrentItem(currentItem);// 切换到下一个页面// 继续延时3秒发消息,形成循环,可以handleMessage方法里发送消息的 mHandler.sendEmptyMessageDelayed(0, 3000); }; }; mHandler.sendEmptyMessageDelayed(0, 3000);// 延时3秒后发消息 } } else {// 如果是加载下一页,需要将数据追加给原来的集合 ArrayList<TabNewsData> news = mTabDetailData.data.news; mNewsList.addAll(news);//这个方法将数据追加给原来的list mNewsAdapter.notifyDataSetChanged(); } } /** * 头条新闻适配器 * * @author Kevin * */ class TopNewsAdapter extends PagerAdapter { private BitmapUtils utils; public TopNewsAdapter() { utils = new BitmapUtils(mActivity); utils.configDefaultLoadingImage(R.drawable.topnews_item_default);// 设置默认图片 } @Override public int getCount() { return mTabDetailData.data.topnews.size(); } @Override public boolean isViewFromObject(View arg0, Object arg1) { return arg0 == arg1; } @Override public Object instantiateItem(ViewGroup container, int position) { ImageView image = new ImageView(mActivity); image.setScaleType(ScaleType.FIT_XY);// 基于控件大小填充图片 TopNewsData topNewsData = mTopNewsList.get(position); utils.display(image, topNewsData.topimage);// 传递imagView对象和图片地址 container.addView(image); image.setOnTouchListener(new TopNewsTouchListener());//设置触摸监听 return image; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View) object); } } /** * 头条新闻的触摸监听 * * @author Kevin * */ class TopNewsTouchListener implements OnTouchListener { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: System.out.println("按下");// 删除Handler中的所有消息,Callbacks是说有个postDelayed,传的是Runnable,这么这个方法每几秒执行一次 mHandler.removeCallbacksAndMessages(null); // mHandler.postDelayed(new Runnable() { // // @Override // public void run() { // // } // }, 3000); break; case MotionEvent.ACTION_CANCEL://因为当按下没抬起,而是滑了一下,那么事件就取消了,需要重新发送一下
System.out.println("事件取消"); mHandler.sendEmptyMessageDelayed(0, 3000); break; case MotionEvent.ACTION_UP: System.out.println("抬起"); mHandler.sendEmptyMessageDelayed(0, 3000); break; default: break; } return true; } } /** * 新闻列表的适配器 * * @author Kevin * */ class NewsAdapter extends BaseAdapter { private BitmapUtils utils; public NewsAdapter() { utils = new BitmapUtils(mActivity); utils.configDefaultLoadingImage(R.drawable.pic_item_list_default); } @Override public int getCount() { return mNewsList.size(); } @Override public TabNewsData getItem(int position) { return mNewsList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = View.inflate(mActivity, R.layout.list_news_item, null); holder = new ViewHolder(); holder.ivPic = (ImageView) convertView .findViewById(R.id.iv_pic); holder.tvTitle = (TextView) convertView .findViewById(R.id.tv_title); holder.tvDate = (TextView) convertView .findViewById(R.id.tv_date); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } TabNewsData item = getItem(position); holder.tvTitle.setText(item.title); holder.tvDate.setText(item.pubdate); utils.display(holder.ivPic, item.listimage); String ids = PrefUtils.getString(mActivity, "read_ids", "");//保存缓存 if (ids.contains(getItem(position).id)) { holder.tvTitle.setTextColor(Color.GRAY); } else { holder.tvTitle.setTextColor(Color.BLACK); } return convertView; } } static class ViewHolder { public TextView tvTitle; public TextView tvDate; public ImageView ivPic; } @Override public void onPageScrollStateChanged(int arg0) { } @Override public void onPageScrolled(int arg0, float arg1, int arg2) { } @Override public void onPageSelected(int arg0) { TopNewsData topNewsData = mTopNewsList.get(arg0); tvTitle.setText(topNewsData.title); }}