有关新鲜事模块的实现前面已聊过一部分,这篇在前面Android仿人人客户端(v5.7.1)——新鲜事之状态这篇的基础上,将剩下部分的实现和在实现过程中代码的调整一一详细的聊聊。
一、基于开源组件XListView实现下拉刷新和向上滚动或点击加载更多。
1、在布局文件(fresh_news.xml)中加入XListView组件,如下
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#FFFFFF" android:orientation="vertical" > <com.everyone.android.widget.TopMenuNavbar android:id="@+id/rl_top_menu_navbar" style="@style/top_navbar" /> <include android:id="@+id/loading" layout="@layout/loading_progressbar" /> <com.everyone.android.widget.XListView android:id="@+id/listview" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="#00000000" android:cacheColorHint="#00000000" android:divider="@drawable/v5_0_1_newsfeed_divider" android:listSelector="@null" android:fastScrollEnabled="true" /> </LinearLayout>
2、在新鲜事视图中做ListView组件替换,让新鲜事视图(FreshNewsLayout)类实现IXListViewListener接口,代码如下:
public class FreshNewsLayout extends FrameLayout implements IXListViewListener, OnClickListener {}
你会发现新鲜事视图(FreshNewsLayout)类中会多如下两个方法:
// 下拉刷新 @Override public void onRefresh() { } // 加载更多 @Override public void onLoadMore() { }
用XListView替换掉原生的 ListView组件,并绑定数据适配器和设置事件监听器。具体代码如下:
mListView = (XListView) freshNewsViewRoot.findViewById(R.id.listview); mListView.setPullLoadEnable(false); mListView.setPullRefreshEnable(false); mListView.setXListViewListener(this); mFreshNewsAdapter = new FreshNewsAdapter(mActivity, mFreshNewsList); mListView.setAdapter(mFreshNewsAdapter);
3、其它的代码不变,运行程序,效果图如下:
点击顶部左侧的menu按钮,效果图如下:
4、添加下拉刷新和向上滚动加载更多,代码如下:
// 下拉刷新 @Override public void onRefresh() { page = 1; getFreshNews(); } // 加载更多 @Override public void onLoadMore() { page++; getFreshNews(); }
下拉刷新,运行效果图如下:
向上滑动加载更多运行,效果图如下:
二、选择顶部下拉列表中的Item,对新鲜事进行过滤处理。
1、再次罗列人人官方API 里提供有关新鲜事类型的一系列数字和各个数字含义见下面:
10 更新状态的新鲜事;11 page更新状态的新鲜事; 20 发表日志的新鲜事;21 分享日志的新鲜事;22 page发表日志的新鲜事;
23 page分享日志的新鲜事;30 上传照片的新鲜事;31 page上传照片的新鲜事;32 分享照片的新鲜事;33 分享相册的新鲜事;
34 修改头像的新鲜事;35 page修改头像的新鲜事; 36 page分享照片的新鲜事;40 成为好友的新鲜事;41 成为page粉丝的新鲜事;
50 分享视频的新鲜事;51 分享链接的新鲜事; 52 分享音乐的新鲜事; 53 page分享视频的新鲜事;54 page分享链接的新鲜事;
55 page分享音乐的新鲜事。
对新鲜事类型进行分类,具体代码如下:
/** * 发表的新鲜事,默认请求所有类型的新鲜事 */ private static final String FRESH_NEWS_TYPE_ALL = "10,11,20,21,22,23,30,31,32,33,34,35,36"; /** * 好友内容 */ private static final String FRESH_NEWS_TYPE_FRIEND = "40"; /** * 特别关注 */ private static final String FRESH_NEWS_TYPE_FOCUS = null; /** * 状态 */ private static final String FRESH_NEWS_TYPE_STATUS = "10,11"; /** * 照片 */ private static final String FRESH_NEWS_TYPE_PHOTO = "30,31"; /** * 位置 */ private static final String FRESH_NEWS_TYPE_PLACE = null; /** * 分享的新鲜事 */ private static final String FRESH_NEWS_TYPE_SHARE = "21,23,32,33,36"; /** * 日志 */ private static final String FRESH_NEWS_TYPE_BLOG = "20,22";
注:人人的官方API里没有提供相关的数字表示特别关注和位置,故我目前暂时给赋值为null。有关音频和视屏的部分暂时未去实现。
2、将新鲜事的分类与顶部下拉列表进行关联。
a.将新鲜事类型放入一个数组中,代码如下:
/** * 新鲜事类型数组 */ private String[] freshNewsTypes = {FRESH_NEWS_TYPE_ALL, FRESH_NEWS_TYPE_FRIEND, FRESH_NEWS_TYPE_FOCUS, FRESH_NEWS_TYPE_STATUS,FRESH_NEWS_TYPE_PHOTO, FRESH_NEWS_TYPE_PLACE, FRESH_NEWS_TYPE_SHARE,FRESH_NEWS_TYPE_BLOG};
b.设置默认的新鲜事类型
/** * 新鲜事类型,默认为当前所支持的全部类型 */ private String fresh_news_type = FRESH_NEWS_TYPE_ALL;
c.顶部下拉列表Item单击事件的处理,代码如下:
mPopupListView.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { mPopupWindow.dismiss(); // 当前选择的项与之前的一样,则不做处理。 if (mPopupAdapter.getPosition() == position) { return; } // 新鲜事过滤事件处理 fresh_news_type = freshNewsTypes[position]; LogUtil.i(TAG, "onItemClick position = " + position); LogUtil.i(TAG, "onItemClick fresh_news_type = " + fresh_news_type); if(fresh_news_type == null){ Toast.makeText(mActivity.getApplicationContext(), "抱歉,您选择的项官方API暂时未提供如何获取相应的数据!", Toast.LENGTH_LONG).show(); return; } mPopupAdapter.setPosition(position); mPopupAdapter.notifyDataSetChanged(); topMenuNavbar.tvTitle.setText(mTexts[position]); page = 1; mFreshNewsList.clear(); getFreshNews(); } });
/** * 向服务器端请求新鲜事的数据 */ public void getFreshNews() { String accessToken = mAuthTokenManager.getAccessToken(); LogUtil.e(TAG, "accessToken = " + accessToken); Map<String, String> parameter = new HashMap<String, String>(); parameter.put("v", "1.0"); // API的版本号,固定值为1.0 parameter.put("access_token", accessToken); // OAuth2.0验证授权后获得的token。 parameter.put("format", "JSON"); // 返回值的格式。请指定为JSON或者XML parameter.put("call_id", "1.0"); // 请求队列号 parameter.put("method", "feed.get"); parameter.put("type", fresh_news_type); // 新鲜事的类别,多个类型以逗号分隔,type列表 // parameter.put("uid", ""); // 支持传入当前用户的一个好友ID,表示获取此好友的新鲜事,如果不传,默认为获取当前用户的新鲜事 parameter.put("page", page + ""); // 支持分页,指定页号,页号从1开始,默认值为1 parameter.put("count", pageCount + ""); // 支持分页,每一页记录数,默认值为30,最大50 AsyncHttpsPost asyncHttpsPost = new AsyncHttpsPost(Constant.API_SERVER_URL, parameter, new ParseCallback() { @Override public LinkedList<FreshNews> parse(String json) throws JSONException { LogUtil.i(TAG, "json = " + json); if ("[]".equals(json)) { return null; } Gson gson = new Gson(); java.lang.reflect.Type type = new TypeToken<LinkedList<FreshNews>>() { }.getType(); LinkedList<FreshNews> freshNewsList = gson.fromJson(json, type); LogUtil.e(TAG, "freshNewsList = " + freshNewsList.size()); return freshNewsList; } }, new ResultCallback() { @Override public void onSuccess(final Object obj) { mHandler.post(new Runnable() { @Override public void run() { if (obj != null && obj instanceof LinkedList) { @SuppressWarnings("unchecked") LinkedList<FreshNews> freshNewsList = (LinkedList<FreshNews>) obj; if (freshNewsList.size() > 0) { // 下拉刷新的数据去重复处理 if (page == 1 && mFreshNewsList.size() > 0) { for (FreshNews freshNews : freshNewsList) { boolean flage = false; for (FreshNews fresh : mFreshNewsList) { if(freshNews.getPost_id() == fresh.getPost_id()){ flage = true; } } if(flage == false){ mFreshNewsList.addFirst(freshNews); } } } else { mFreshNewsList.addAll(freshNewsList); } mListView.setPullLoadEnable(true); mListView.setPullRefreshEnable(true); } } else { mListView.setPullLoadEnable(false); } if (page == 1) { if (mLoadingView.isShown()) { mLoadingView.setVisibility(View.GONE); } mFreshNewsAdapter.notifyDataSetInvalidated(); mListView.stopRefresh(); } else { mFreshNewsAdapter.notifyDataSetChanged(); mListView.stopLoadMore(); } // 设置刷新时间 String refreshTime = mDateFormat.format(System.currentTimeMillis()); mListView.setRefreshTime(refreshTime); } }); } @Override public void onFail(int errorCode) { LogUtil.i(TAG, "freshNewsList errorCode = " + errorCode); } }); mDefaultThreadPool.execute(asyncHttpsPost); mAsyncRequests.add(asyncHttpsPost); }
先看下运行后的效果图,按状态过滤,运行效果图如下:
按照片过滤,运行效果图如下:
按分享过滤,运行效果图如下:
按日志过滤,运行效果图如下:
e.看完效果图,我们来看看ListView的Item布局文件(fresh_news_list_item.xml),配置内容如下:
<?xml version="1.0" encoding="UTF-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="@drawable/fresh_news_list_item_selector" > <!-- 头像 --> <ImageView android:id="@+id/iv_user_image" android:layout_width="43.0dip" android:layout_height="43.0dip" android:layout_marginLeft="10.0dip" android:layout_marginTop="8.0dip" android:saveEnabled="true" android:scaleType="center" android:src="@drawable/v5_0_1_widget_default_head" /> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginLeft="8dip" android:layout_marginRight="8dip" android:layout_marginTop="8dip" android:layout_toRightOf="@+id/iv_user_image" android:orientation="vertical" android:paddingBottom="8dip" > <!-- 昵称 --> <TextView android:id="@+id/tv_nickname" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="5.0dip" android:textColor="#ff005092" android:textSize="16sp" /> <!-- 用户自定义输入的内容 --> <TextView android:id="@+id/tv_message_content" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginBottom="5.0dip" android:textColor="#000000" android:textSize="15.0sp" /> <!-- 状态 --> <include android:id="@+id/ll_status" layout="@layout/fresh_news_item_state" android:visibility="gone" /> <!-- 图片 --> <include android:id="@+id/ll_photo" layout="@layout/fresh_news_item_photo" android:visibility="gone" /> <!-- 日志 --> <include android:id="@+id/ll_blog" layout="@layout/fresh_news_item_blog" android:visibility="gone" /> <!-- 更新头像 --> <ImageView android:id="@+id/iv_update_icon" android:layout_width="80dip" android:layout_height="80dip" android:scaleType="centerCrop" android:visibility="gone" /> <!-- 时间和来源 --> <RelativeLayout android:layout_width="fill_parent" android:layout_height="wrap_content" > <!-- 时间 --> <TextView android:id="@+id/tv_published" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8.0dip" android:drawablePadding="3dip" android:textColor="#ff666666" android:textSize="12.0sp" /> <!-- 来源 --> <TextView android:id="@+id/tv_source" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="15dip" android:layout_marginTop="8.0dip" android:layout_toRightOf="@+id/tv_published" android:textColor="#ff666666" android:textSize="12.0sp" /> </RelativeLayout> <!-- 评论信息 --> <LinearLayout android:id="@+id/ll_comments_content" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="7.0dip" android:background="@drawable/fresh_news_popup" android:orientation="vertical" android:padding="10dip" android:visibility="gone" /> </LinearLayout> </RelativeLayout>
其中包含的有关状态的 布局文件(fresh_news_item_state.xml),内容如下:
<?xml version="1.0" encoding="UTF-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <View android:layout_width="2dip" android:layout_height="fill_parent" android:background="#20333333" /> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginLeft="10dip" android:orientation="vertical" > <TextView android:id="@+id/tv_status_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ff005092" android:textSize="14sp" /> <TextView android:id="@+id/tv_status_content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dip" android:textColor="#ff888888" android:textSize="14sp" /> </LinearLayout> </LinearLayout>
其中包含的有关照片的 布局文件(fresh_news_item_photo.xml),内容如下:
<?xml version="1.0" encoding="UTF-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <View android:id="@+id/v_photo_left_line" android:layout_width="2dip" android:layout_height="fill_parent" android:layout_marginRight="10dip" android:background="#20333333" /> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" > <TextView android:id="@+id/tv_photo_owner_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ff005092" android:textSize="14sp" /> <TextView android:id="@+id/tv_photo_describe" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dip" android:textColor="#000000" android:textSize="13sp" /> <ImageView android:id="@+id/iv_photo_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dip" /> <!-- 人人官方的做法,显示固定大小的图片,并对原图进行剪裁。根据设置的宽高选择适中的区域进行裁剪--> <!-- <ImageView android:id="@+id/iv_photo_image" android:layout_width="150dip" android:layout_height="150dip" android:layout_marginTop="10dip" android:scaleType="centerCrop" /> --> <TextView android:id="@+id/tv_photo_source" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dip" android:textColor="#ff888888" android:textSize="13sp" /> </LinearLayout> </LinearLayout>
其中包含的有关日志的 布局文件(fresh_news_item_blog.xml),内容如下:
<?xml version="1.0" encoding="UTF-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <View android:id="@+id/v_blog_left_line" android:layout_width="2dip" android:layout_height="fill_parent" android:layout_marginRight="10dip" android:background="#20333333" /> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" > <TextView android:id="@+id/tv_blog_owner_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ff005092" android:textSize="14sp" /> <TextView android:id="@+id/tv_blog_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dip" android:layout_marginTop="8dip" android:textColor="#ff005092" android:textSize="14sp" /> <TextView android:id="@+id/tv_blog_describe" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ff888888" android:textSize="14sp" /> </LinearLayout> </LinearLayout>
三、完整代码
1、组件XListView的数据适配器(FreshNewsAdapter)的源码如下:
package com.everyone.android.ui.freshnews; import java.util.LinkedList; import android.graphics.Color; import android.text.TextUtils; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.everyone.android.R; import com.everyone.android.bitmap.ImageLoader; import com.everyone.android.entity.Attachment; import com.everyone.android.entity.Comment; import com.everyone.android.entity.Comments; import com.everyone.android.entity.FreshNews; import com.everyone.android.entity.ImageInfo; import com.everyone.android.entity.Source; import com.everyone.android.ui.EveryoneActivity; import com.everyone.android.utils.DensityUtil; import com.everyone.android.utils.LogUtil; /** * 功能描述:新鲜事列表数据适配器 * @author android_ls */ public class FreshNewsAdapter extends BaseAdapter { /** * LOG打印标签 */ private static final String TAG = "FreshNewsAdapter"; private LayoutInflater inflater; private LinkedList<FreshNews> mFreshNewsList; private EveryoneActivity mActivity; private ImageLoader mImageLoader; public FreshNewsAdapter(EveryoneActivity activity, LinkedList<FreshNews> freshNewsList) { inflater = LayoutInflater.from(activity); mActivity = activity; mFreshNewsList = freshNewsList; this.mImageLoader = new ImageLoader(mActivity); } @Override public int getCount() { return mFreshNewsList.size(); } @Override public Object getItem(int arg0) { return mFreshNewsList.get(arg0); } @Override public long getItemId(int position) { return position; } /* (non-Javadoc) * @see android.widget.Adapter#getView(int, android.view.View, android.view.ViewGroup) */ @Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { convertView = inflater.inflate(R.layout.fresh_news_list_item, null); holder = new ViewHolder(); holder.imageView1 = (ImageView) convertView.findViewById(R.id.iv_user_image); holder.text1 = (TextView) convertView.findViewById(R.id.tv_nickname); holder.text2 = (TextView) convertView.findViewById(R.id.tv_message_content); holder.linearLayout1 = (LinearLayout) convertView.findViewById(R.id.ll_comments_content); holder.linearLayout2 = (LinearLayout) convertView.findViewById(R.id.ll_status); holder.linearLayout3 = (LinearLayout) convertView.findViewById(R.id.ll_photo); holder.linearLayout4 = (LinearLayout) convertView.findViewById(R.id.ll_blog); holder.text7 = (TextView) convertView.findViewById(R.id.tv_photo_owner_name); holder.text8 = (TextView) convertView.findViewById(R.id.tv_photo_describe); holder.imageView2 = (ImageView) convertView.findViewById(R.id.iv_photo_image); holder.text9 = (TextView) convertView.findViewById(R.id.tv_photo_source); holder.text10 = (TextView) convertView.findViewById(R.id.tv_blog_owner_name); holder.text11 = (TextView) convertView.findViewById(R.id.tv_blog_title); holder.text12 = (TextView) convertView.findViewById(R.id.tv_blog_describe); holder.imageView3 = (ImageView) convertView.findViewById(R.id.iv_update_icon); holder.text3 = (TextView) convertView.findViewById(R.id.tv_published); holder.text4 = (TextView) convertView.findViewById(R.id.tv_source); holder.text5 = (TextView) convertView.findViewById(R.id.tv_status_name); holder.text6 = (TextView) convertView.findViewById(R.id.tv_status_content); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } final FreshNews freshNews = mFreshNewsList.get(position); // 姓名 holder.text1.setText(freshNews.getName()); LogUtil.i(TAG, "freshNews.getName() = " + freshNews.getName()); LogUtil.i(TAG, "description = " + freshNews.getDescription()); LogUtil.i(TAG, "freshNews.getMessage() = " + freshNews.getMessage()); LogUtil.i(TAG, "freshNews.getTitle() = " + freshNews.getTitle()); // 加载图像 String headurl = freshNews.getHeadurl(); LogUtil.i(TAG, "headurl = " + headurl); if (!TextUtils.isEmpty(headurl)) { int widthPx = DensityUtil.dip2px(mActivity, 43); ImageInfo imgInfo = new ImageInfo(holder.imageView1, headurl, widthPx, widthPx); mImageLoader.displayImage(imgInfo); } // 用户自定义输入的内容 String message = freshNews.getMessage(); if (!TextUtils.isEmpty(message)) { holder.text2.setVisibility(View.VISIBLE); holder.text2.setText(message); } else { holder.text2.setVisibility(View.GONE); } // 内容的前缀 String prefix = freshNews.getPrefix(); LogUtil.i(TAG, "freshNews.getPrefix() = " + prefix); holder.linearLayout2.setVisibility(View.GONE); holder.linearLayout3.setVisibility(View.GONE); holder.linearLayout4.setVisibility(View.GONE); holder.imageView3.setVisibility(View.GONE); // 新鲜事中包含的媒体内容 LinkedList<Attachment> attachments = freshNews.getAttachment(); if (attachments != null) { int size = attachments.size(); LogUtil.i(TAG, "size = " + size); for (int i = 0; i < size; i++) { Attachment att = attachments.get(i); String media_type = att.getMedia_type(); LogUtil.i(TAG, "media_type = " + media_type); LogUtil.i(TAG, "att.getContent() = " + att.getContent()); LogUtil.i(TAG, "getHref = " + att.getHref()); LogUtil.i(TAG, "att.getOwner_id() = " + att.getOwner_id()); LogUtil.i(TAG, "getOwner_name() = " + att.getOwner_name()); LogUtil.i(TAG, "getRaw_src() = " + att.getRaw_src()); LogUtil.i(TAG, "getScr() = " + att.getScr()); if ("status".equals(media_type)) { holder.linearLayout2.setVisibility(View.VISIBLE); holder.text5.setText(att.getOwner_name()); holder.text6.setText(att.getContent()); } else if ("photo".equals(media_type)) { holder.linearLayout3.setVisibility(View.VISIBLE); holder.imageView2.setImageBitmap(null); String raw_src = att.getRaw_src(); if(raw_src != null){ ImageInfo imgInfo = new ImageInfo(holder.imageView2, raw_src); mImageLoader.displayImage(imgInfo); } String owner_name = att.getOwner_name(); if(!TextUtils.isEmpty(owner_name)){ holder.text7.setVisibility(View.VISIBLE); holder.text7.setText(owner_name); } else { holder.text7.setVisibility(View.GONE); } String description = freshNews.getDescription(); if(!TextUtils.isEmpty(description)){ holder.text8.setVisibility(View.VISIBLE); holder.text8.setText(description); } else { holder.text8.setVisibility(View.GONE); } holder.text9.setText("【" + freshNews.getTitle() + "】"); }else if ("blog".equals(media_type)) { holder.linearLayout4.setVisibility(View.VISIBLE); holder.text10.setText(att.getOwner_name()); holder.text11.setText(freshNews.getTitle()); holder.text12.setText(freshNews.getDescription()); }else if ("link".equals(media_type)) { } else if ("album".equals(media_type)) { } else if ("link".equals(media_type)) { } else if ("video".equals(media_type)) { } else if ("audio".equals(media_type)) { } } } // page代表公共主页新鲜事 int feedType = freshNews.getFeed_type(); LogUtil.i(TAG, "feedType = " + feedType); switch (feedType) { case 10: // 更新状态的新鲜事。 case 11: // page更新状态的新鲜事。 // 设置状态标识图标 holder.text3.setCompoundDrawablesWithIntrinsicBounds(R.drawable.v5_0_1_newsfeed_status_icon, 0, 0, 0); break; case 20: // 发表日志的新鲜事。 case 22: // page发表日志的新鲜事。 holder.text3.setCompoundDrawablesWithIntrinsicBounds(R.drawable.v5_0_1_newsfeed_blog_icon, 0, 0, 0); holder.linearLayout4.setVisibility(View.VISIBLE); holder.linearLayout4.findViewById(R.id.v_blog_left_line).setVisibility(View.GONE); holder.text10.setVisibility(View.GONE); if (!TextUtils.isEmpty(prefix)) { holder.text2.setVisibility(View.VISIBLE); holder.text2.setText(prefix); } else { holder.text2.setVisibility(View.GONE); } holder.text11.setText(freshNews.getTitle()); holder.text12.setText(freshNews.getDescription()); break; case 30: // 上传照片的新鲜事。 case 31: // page上传照片的新鲜事。 holder.text3.setCompoundDrawablesWithIntrinsicBounds(R.drawable.v5_0_1_newsfeed_photo_icon, 0, 0, 0); holder.linearLayout3.findViewById(R.id.v_photo_left_line).setVisibility(View.GONE); break; case 21: // 分享日志的新鲜事。 case 23: // page分享日志的新鲜事。 case 32: // 分享照片的新鲜事。 case 33: // 分享相册的新鲜事。 // 设置分享标识图标 holder.text3.setCompoundDrawablesWithIntrinsicBounds(R.drawable.v5_0_1_newsfeed_share_icon, 0, 0, 0); if(feedType == 32 || feedType == 33){ holder.linearLayout3.findViewById(R.id.v_photo_left_line).setVisibility(View.VISIBLE); } break; case 34: // 修改头像的新鲜事。 case 35: // page修改头像的新鲜事。 holder.text2.setVisibility(View.VISIBLE); holder.text2.setText("更新头像"); holder.imageView3.setVisibility(View.VISIBLE); ImageInfo imgInfo = new ImageInfo(holder.imageView3, headurl); mImageLoader.displayImage(imgInfo); break; // ... default: break; } // 动态生成显示评论信息的Item Comments comments = freshNews.getComments(); if (comments != null) { LinkedList<Comment> commentList = comments.getComment(); if (commentList != null) { holder.linearLayout1.setVisibility(View.VISIBLE); if (holder.linearLayout1.getChildCount() > 0) { holder.linearLayout1.removeAllViews(); } int count = comments.getCount(); if (count > 0) { TextView tvCount = new TextView(mActivity); tvCount.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); tvCount.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); tvCount.setSingleLine(); tvCount.setCompoundDrawablePadding(5); tvCount.setPadding(0, 10, 0, 0); tvCount.setText(count + "条评论"); tvCount.setTextColor(Color.parseColor("#ff005092")); tvCount.setCompoundDrawablesWithIntrinsicBounds(R.drawable.fresh_news_comment_icon, 0, 0, 0); holder.linearLayout1.addView(tvCount); } int size = commentList.size(); LogUtil.i(TAG, "commentList size = " + size); for (int i = 0; i < size; i++) { Comment comment = commentList.get(i); LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); TextView tvContent = new TextView(mActivity); tvContent.setLayoutParams(layoutParams); tvContent.setTextColor(Color.BLACK); tvContent.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); tvContent.setSingleLine(); tvContent.setPadding(0, 10, 0, 0); tvContent.setText(comment.getName() + ":" + comment.getText()); holder.linearLayout1.addView(tvContent); TextView tvTime = new TextView(mActivity); tvTime.setTextColor(Color.GRAY); tvTime.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); tvTime.setLayoutParams(layoutParams); tvContent.setPadding(0, 5, 0, 10); tvTime.setText(comment.getTime()); holder.linearLayout1.addView(tvTime); } } else { holder.linearLayout1.setVisibility(View.GONE); } } else { holder.linearLayout1.setVisibility(View.GONE); } // 对获取的时间字符串的处理 String updateTime = freshNews.getUpdate_time(); if (!TextUtils.isEmpty(updateTime)) { updateTime = updateTime.substring(updateTime.indexOf("-") + 1, updateTime.lastIndexOf(":")); updateTime = updateTime.replace("-", "月"); updateTime = updateTime.replace(" ", "日 "); int index = updateTime.indexOf("0"); if (index == 0) { updateTime = updateTime.substring(index + 1); } holder.text3.setText(updateTime); } // 来自那种客户端 Source source = freshNews.getSource(); if (source != null) { holder.text4.setText("来自:" + source.getText()); } return convertView; } static class ViewHolder { public LinearLayout linearLayout1; public LinearLayout linearLayout2; public LinearLayout linearLayout3; public LinearLayout linearLayout4; public ImageView imageView1; public ImageView imageView2; public ImageView imageView3; public TextView text1; public TextView text2; public TextView text3; public TextView text4; public TextView text5; public TextView text6; public TextView text7; public TextView text8; public TextView text9; public TextView text10; public TextView text11; public TextView text12; } }
2、新鲜事视图完整源码如下:
package com.everyone.android.ui.freshnews; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.json.JSONException; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.PopupWindow; import android.widget.Toast; import com.everyone.android.R; import com.everyone.android.api.AuthTokenManager; import com.everyone.android.callback.ParseCallback; import com.everyone.android.callback.ResultCallback; import com.everyone.android.entity.FreshNews; import com.everyone.android.net.AsyncBaseRequest; import com.everyone.android.net.AsyncHttpsPost; import com.everyone.android.net.DefaultThreadPool; import com.everyone.android.ui.EveryoneActivity; import com.everyone.android.utils.Constant; import com.everyone.android.utils.LogUtil; import com.everyone.android.widget.TopMenuNavbar; import com.everyone.android.widget.XListView; import com.everyone.android.widget.XListView.IXListViewListener; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; /** * 功能描述:新鲜事视图 * @author android_ls */ public class FreshNewsLayout extends FrameLayout implements IXListViewListener, OnClickListener { /** * LOG打印标签 */ private static final String TAG = "FreshNewsLayout"; private TopMenuNavbar topMenuNavbar; /** * 每一页记录数,默认值为30,最大50 */ private int pageCount = 30; /** * 当前获取第几页,默认值为1 */ private int page = 1; public TopMenuNavbar getTopMenuNavbar() { return topMenuNavbar; } private EveryoneActivity mActivity; private List<AsyncBaseRequest> mAsyncRequests; private DefaultThreadPool mDefaultThreadPool; private Handler mHandler; public AuthTokenManager mAuthTokenManager; private LinearLayout mLoadingView; private XListView mListView; private FreshNewsAdapter mFreshNewsAdapter; /** * 新鲜事信息集合 */ private LinkedList<FreshNews> mFreshNewsList = new LinkedList<FreshNews>(); private PopupWindow mPopupWindow; /** * 顶部下拉列表 */ private ListView mPopupListView; /** * 顶部下拉列表数据适配器 */ private FreshNewsPopupAdapter mPopupAdapter; /** * 顶部下拉列表的操作提示文本数组 */ private String[] mTexts; /** * 顶部下拉列表的操作指示图标Id数组 */ private int[] mIcons = { R.drawable.v5_0_1_newsfeed_popupwindow_type_all_background, R.drawable.v5_0_1_newsfeed_popupwindow_type_friend_background, R.drawable.v5_0_1_newsfeed_popupwindow_type_specialfocus_background, R.drawable.v5_0_1_newsfeed_popupwindow_type_status_background, R.drawable.v5_0_1_newsfeed_popupwindow_type_photo_background, R.drawable.v5_0_1_newsfeed_popupwindow_type_place_background, R.drawable.v5_0_1_newsfeed_popupwindow_type_share_background, R.drawable.v5_0_1_newsfeed_popupwindow_type_blog_background }; /* 10 更新状态的新鲜事。 11 page更新状态的新鲜事。 20 发表日志的新鲜事。 21 分享日志的新鲜事。 22 page发表日志的新鲜事。 23 page分享日志的新鲜事。 30 上传照片的新鲜事。 31 page上传照片的新鲜事。 32 分享照片的新鲜事。 33 分享相册的新鲜事。 34 修改头像的新鲜事。 35 page修改头像的新鲜事。 36 page分享照片的新鲜事。 40 成为好友的新鲜事。 41 成为page粉丝的新鲜事。 50 分享视频的新鲜事。 51 分享链接的新鲜事。 52 分享音乐的新鲜事。 53 page分享视频的新鲜事。 54 page分享链接的新鲜事。 55 page分享音乐的新鲜事。 */ /** * 发表的新鲜事,默认请求所有类型的新鲜事 */ private static final String FRESH_NEWS_TYPE_ALL = "10,11,20,21,22,23,30,31,32,33,34,35,36"; /** * 好友内容 */ private static final String FRESH_NEWS_TYPE_FRIEND = "40"; /** * 特别关注 */ private static final String FRESH_NEWS_TYPE_FOCUS = null; /** * 状态 */ private static final String FRESH_NEWS_TYPE_STATUS = "10,11"; /** * 照片 */ private static final String FRESH_NEWS_TYPE_PHOTO = "30,31"; /** * 位置 */ private static final String FRESH_NEWS_TYPE_PLACE = null; /** * 分享的新鲜事 */ private static final String FRESH_NEWS_TYPE_SHARE = "21,23,32,33,36"; /** * 日志 */ private static final String FRESH_NEWS_TYPE_BLOG = "20,22"; /** * 新鲜事类型数组 */ private String[] freshNewsTypes = {FRESH_NEWS_TYPE_ALL, FRESH_NEWS_TYPE_FRIEND, FRESH_NEWS_TYPE_FOCUS, FRESH_NEWS_TYPE_STATUS,FRESH_NEWS_TYPE_PHOTO, FRESH_NEWS_TYPE_PLACE, FRESH_NEWS_TYPE_SHARE,FRESH_NEWS_TYPE_BLOG}; /** * 新鲜事类型,默认为当前所支持的全部类型 */ private String fresh_news_type = FRESH_NEWS_TYPE_ALL; private SimpleDateFormat mDateFormat; public FreshNewsLayout(EveryoneActivity activity) { super(activity); mActivity = activity; this.mAsyncRequests = activity.getAsyncRequests(); this.mDefaultThreadPool = activity.getDefaultThreadPool(); this.mHandler = activity.getHandler(); this.mAuthTokenManager = activity.getAuthTokenManager(); mDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); setupViews(); // 初始化下拉列表 setupPopupWindow(); } public FreshNewsLayout(Context context, AttributeSet attrs) { super(context, attrs); setupViews(); } private void setupPopupWindow() { mTexts = mActivity.getResources().getStringArray(R.array.fresh_news_filter_list); View view = LayoutInflater.from(mActivity).inflate(R.layout.fresh_news_popupwindow, null); mPopupWindow = new PopupWindow(view, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT, true); mPopupWindow.setAnimationStyle(R.style.fresh_news_popup_animation); mPopupListView = (ListView) view.findViewById(R.id.popup_listview); mPopupAdapter = new FreshNewsPopupAdapter(mActivity, mIcons, mTexts); mPopupListView.setAdapter(mPopupAdapter); mPopupListView.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { mPopupWindow.dismiss(); // 当前选择的项与之前的一样,则不做处理。 if (mPopupAdapter.getPosition() == position) { return; } // 新鲜事过滤事件处理 fresh_news_type = freshNewsTypes[position]; LogUtil.i(TAG, "onItemClick position = " + position); LogUtil.i(TAG, "onItemClick fresh_news_type = " + fresh_news_type); if(fresh_news_type == null){ Toast.makeText(mActivity.getApplicationContext(), "抱歉,您选择的项官方API暂时未提供如何获取相应的数据!", Toast.LENGTH_LONG).show(); return; } mPopupAdapter.setPosition(position); mPopupAdapter.notifyDataSetChanged(); topMenuNavbar.tvTitle.setText(mTexts[position]); page = 1; mFreshNewsList.clear(); getFreshNews(); } }); } private void setupViews() { final LayoutInflater mLayoutInflater = LayoutInflater.from(getContext()); LinearLayout freshNewsViewRoot = (LinearLayout) mLayoutInflater.inflate(R.layout.fresh_news, null); addView(freshNewsViewRoot); // 加载提示进度条 mLoadingView = (LinearLayout) freshNewsViewRoot.findViewById(R.id.loading); topMenuNavbar = (TopMenuNavbar) freshNewsViewRoot.findViewById(R.id.rl_top_menu_navbar); topMenuNavbar.mLlDownList.setOnClickListener(this); topMenuNavbar.mLlRefresh.setOnClickListener(this); topMenuNavbar.ivRightLine.setVisibility(View.GONE); topMenuNavbar.tvRightOperationName.setVisibility(View.GONE); mListView = (XListView) freshNewsViewRoot.findViewById(R.id.listview); mListView.setPullLoadEnable(false); mListView.setPullRefreshEnable(false); mListView.setXListViewListener(this); mFreshNewsAdapter = new FreshNewsAdapter(mActivity, mFreshNewsList); mListView.setAdapter(mFreshNewsAdapter); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.ll_down_list: if (mPopupWindow != null) { mPopupWindow.showAsDropDown(topMenuNavbar); } break; case R.id.ll_refresh: // mHeaderView.setState(XListViewHeader.STATE_REFRESHING); // page = 1; // getFreshNews(); break; default: break; } } // 下拉刷新 @Override public void onRefresh() { page = 1; getFreshNews(); } // 加载更多 @Override public void onLoadMore() { page++; getFreshNews(); } /** * 向服务器端请求新鲜事的数据 */ public void getFreshNews() { String accessToken = mAuthTokenManager.getAccessToken(); LogUtil.e(TAG, "accessToken = " + accessToken); Map<String, String> parameter = new HashMap<String, String>(); parameter.put("v", "1.0"); // API的版本号,固定值为1.0 parameter.put("access_token", accessToken); // OAuth2.0验证授权后获得的token。 parameter.put("format", "JSON"); // 返回值的格式。请指定为JSON或者XML parameter.put("call_id", "1.0"); // 请求队列号 parameter.put("method", "feed.get"); parameter.put("type", fresh_news_type); // 新鲜事的类别,多个类型以逗号分隔,type列表 // parameter.put("uid", ""); // 支持传入当前用户的一个好友ID,表示获取此好友的新鲜事,如果不传,默认为获取当前用户的新鲜事 parameter.put("page", page + ""); // 支持分页,指定页号,页号从1开始,默认值为1 parameter.put("count", pageCount + ""); // 支持分页,每一页记录数,默认值为30,最大50 AsyncHttpsPost asyncHttpsPost = new AsyncHttpsPost(Constant.API_SERVER_URL, parameter, new ParseCallback() { @Override public LinkedList<FreshNews> parse(String json) throws JSONException { LogUtil.i(TAG, "json = " + json); if ("[]".equals(json)) { return null; } Gson gson = new Gson(); java.lang.reflect.Type type = new TypeToken<LinkedList<FreshNews>>() { }.getType(); LinkedList<FreshNews> freshNewsList = gson.fromJson(json, type); LogUtil.e(TAG, "freshNewsList = " + freshNewsList.size()); return freshNewsList; } }, new ResultCallback() { @Override public void onSuccess(final Object obj) { mHandler.post(new Runnable() { @Override public void run() { if (obj != null && obj instanceof LinkedList) { @SuppressWarnings("unchecked") LinkedList<FreshNews> freshNewsList = (LinkedList<FreshNews>) obj; if (freshNewsList.size() > 0) { // 下拉刷新的数据去重复处理 if (page == 1 && mFreshNewsList.size() > 0) { for (FreshNews freshNews : freshNewsList) { boolean flage = false; for (FreshNews fresh : mFreshNewsList) { if(freshNews.getPost_id() == fresh.getPost_id()){ flage = true; } } if(flage == false){ mFreshNewsList.addFirst(freshNews); } } } else { mFreshNewsList.addAll(freshNewsList); } mListView.setPullLoadEnable(true); mListView.setPullRefreshEnable(true); } } else { mListView.setPullLoadEnable(false); } if (page == 1) { if (mLoadingView.isShown()) { mLoadingView.setVisibility(View.GONE); } mFreshNewsAdapter.notifyDataSetInvalidated(); mListView.stopRefresh(); } else { mFreshNewsAdapter.notifyDataSetChanged(); mListView.stopLoadMore(); } // 设置刷新时间 String refreshTime = mDateFormat.format(System.currentTimeMillis()); mListView.setRefreshTime(refreshTime); } }); } @Override public void onFail(int errorCode) { LogUtil.i(TAG, "freshNewsList errorCode = " + errorCode); } }); mDefaultThreadPool.execute(asyncHttpsPost); mAsyncRequests.add(asyncHttpsPost); } }
3、 新鲜事视图顶部,下拉列表数据适配器的完整源码如下:
package com.everyone.android.ui.freshnews; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.CheckBox; import android.widget.ImageView; import com.everyone.android.R; /** * 功能描述:新鲜事视图顶部下拉列表数据适配器 * @author android_ls * */ public class FreshNewsPopupAdapter extends BaseAdapter { private LayoutInflater mInflater; private int[] mIcon; private String[] mName; private int mPosition; public int getPosition() { return mPosition; } public void setPosition(int mPosition) { this.mPosition = mPosition; } public FreshNewsPopupAdapter(Context context, int[] icon, String[] name) { mInflater = LayoutInflater.from(context); mIcon = icon; mName = name; } public int getCount() { return mIcon.length; } public Object getItem(int position) { return null; } public long getItemId(int position) { return position; } public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { convertView = mInflater.inflate(R.layout.fresh_news_popupwindow_item, null); holder = new ViewHolder(); holder.checkBox = (CheckBox) convertView.findViewById(R.id.cb_text_icon); holder.imageView = (ImageView) convertView.findViewById(R.id.iv_checked); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.checkBox.setButtonDrawable(mIcon[position]); holder.checkBox.setText(mName[position]); holder.checkBox.setChecked(false); holder.imageView.setVisibility(View.GONE); if (position == mPosition) { holder.checkBox.setChecked(true); holder.imageView.setVisibility(View.VISIBLE); } return convertView; } static class ViewHolder { CheckBox checkBox; ImageView imageView; } }
四、开源组件的源码
1、XListView组件的源码:
/** * @file XListView.java * @package me.maxwin.view * @create Mar 18, 2012 6:28:41 PM * @author Maxwin * @description An ListView support (a) Pull down to refresh, (b) Pull up to load more. * Implement IXListViewListener, and see stopRefresh() / stopLoadMore(). */ package com.everyone.android.widget; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.animation.DecelerateInterpolator; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.Scroller; import android.widget.TextView; import com.everyone.android.R; public class XListView extends ListView implements OnScrollListener { private float mLastY = -1; // save event y private Scroller mScroller; // used for scroll back private OnScrollListener mScrollListener; // user's scroll listener // the interface to trigger refresh and load more. private IXListViewListener mListViewListener; // -- header view private XListViewHeader mHeaderView; // header view content, use it to calculate the Header's height. And hide it // when disable pull refresh. private RelativeLayout mHeaderViewContent; private TextView mHeaderTimeView; private int mHeaderViewHeight; // header view's height private boolean mEnablePullRefresh = true; private boolean mPullRefreshing = false; // is refreashing. // -- footer view private XListViewFooter mFooterView; private boolean mEnablePullLoad; private boolean mPullLoading; private boolean mIsFooterReady = false; // total list items, used to detect is at the bottom of listview. private int mTotalItemCount; // for mScroller, scroll back from header or footer. private int mScrollBack; private final static int SCROLLBACK_HEADER = 0; private final static int SCROLLBACK_FOOTER = 1; private final static int SCROLL_DURATION = 400; // scroll back duration private final static int PULL_LOAD_MORE_DELTA = 50; // when pull up >= 50px // at bottom, trigger // load more. private final static float OFFSET_RADIO = 1.8f; // support iOS like pull // feature. /** * @param context */ public XListView(Context context) { super(context); initWithContext(context); } public XListView(Context context, AttributeSet attrs) { super(context, attrs); initWithContext(context); } public XListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initWithContext(context); } private void initWithContext(Context context) { mScroller = new Scroller(context, new DecelerateInterpolator()); // XListView need the scroll event, and it will dispatch the event to // user's listener (as a proxy). super.setOnScrollListener(this); // init header view mHeaderView = new XListViewHeader(context); mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.xlistview_header_content); mHeaderTimeView = (TextView) mHeaderView.findViewById(R.id.xlistview_header_time); addHeaderView(mHeaderView); // init footer view mFooterView = new XListViewFooter(context); // init header height mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mHeaderViewHeight = mHeaderViewContent.getHeight(); getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } @Override public void setAdapter(ListAdapter adapter) { // make sure XListViewFooter is the last footer view, and only add once. if (mIsFooterReady == false) { mIsFooterReady = true; addFooterView(mFooterView); } super.setAdapter(adapter); } /** * enable or disable pull down refresh feature. * * @param enable */ public void setPullRefreshEnable(boolean enable) { mEnablePullRefresh = enable; if (!mEnablePullRefresh) { // disable, hide the content mHeaderViewContent.setVisibility(View.INVISIBLE); } else { mHeaderViewContent.setVisibility(View.VISIBLE); } } /** * enable or disable pull up load more feature. * * @param enable */ public void setPullLoadEnable(boolean enable) { mEnablePullLoad = enable; if (!mEnablePullLoad) { mFooterView.hide(); mFooterView.setOnClickListener(null); } else { mPullLoading = false; mFooterView.show(); mFooterView.setState(XListViewFooter.STATE_NORMAL); // both "pull up" and "click" will invoke load more. mFooterView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { startLoadMore(); } }); } } /** * stop refresh, reset header view. */ public void stopRefresh() { if (mPullRefreshing == true) { mPullRefreshing = false; resetHeaderHeight(); } } /** * stop load more, reset footer view. */ public void stopLoadMore() { if (mPullLoading == true) { mPullLoading = false; mFooterView.setState(XListViewFooter.STATE_NORMAL); } } /** * set last refresh time * * @param time */ public void setRefreshTime(String time) { mHeaderTimeView.setText(time); } private void invokeOnScrolling() { if (mScrollListener instanceof OnXScrollListener) { OnXScrollListener l = (OnXScrollListener) mScrollListener; l.onXScrolling(this); } } private void updateHeaderHeight(float delta) { mHeaderView.setVisiableHeight((int) delta + mHeaderView.getVisiableHeight()); if (mEnablePullRefresh && !mPullRefreshing) { // 未处于刷新状态,更新箭头 if (mHeaderView.getVisiableHeight() > mHeaderViewHeight) { mHeaderView.setState(XListViewHeader.STATE_READY); } else { mHeaderView.setState(XListViewHeader.STATE_NORMAL); } } setSelection(0); // scroll to top each time } /** * reset header view's height. */ private void resetHeaderHeight() { int height = mHeaderView.getVisiableHeight(); if (height == 0) // not visible. return; // refreshing and header isn't shown fully. do nothing. if (mPullRefreshing && height <= mHeaderViewHeight) { return; } int finalHeight = 0; // default: scroll back to dismiss header. // is refreshing, just scroll back to show all the header. if (mPullRefreshing && height > mHeaderViewHeight) { finalHeight = mHeaderViewHeight; } mScrollBack = SCROLLBACK_HEADER; mScroller.startScroll(0, height, 0, finalHeight - height, SCROLL_DURATION); // trigger computeScroll invalidate(); } private void updateFooterHeight(float delta) { int height = mFooterView.getBottomMargin() + (int) delta; if (mEnablePullLoad && !mPullLoading) { if (height > PULL_LOAD_MORE_DELTA) { // height enough to invoke load // more. mFooterView.setState(XListViewFooter.STATE_READY); } else { mFooterView.setState(XListViewFooter.STATE_NORMAL); } } mFooterView.setBottomMargin(height); // setSelection(mTotalItemCount - 1); // scroll to bottom } private void resetFooterHeight() { int bottomMargin = mFooterView.getBottomMargin(); if (bottomMargin > 0) { mScrollBack = SCROLLBACK_FOOTER; mScroller.startScroll(0, bottomMargin, 0, -bottomMargin, SCROLL_DURATION); invalidate(); } } private void startLoadMore() { mPullLoading = true; mFooterView.setState(XListViewFooter.STATE_LOADING); if (mListViewListener != null) { mListViewListener.onLoadMore(); } } @Override public boolean onTouchEvent(MotionEvent ev) { if (mLastY == -1) { mLastY = ev.getRawY(); } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mLastY = ev.getRawY(); break; case MotionEvent.ACTION_MOVE: final float deltaY = ev.getRawY() - mLastY; mLastY = ev.getRawY(); if (getFirstVisiblePosition() == 0 && (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) { // the first item is showing, header has shown or pull down. updateHeaderHeight(deltaY / OFFSET_RADIO); invokeOnScrolling(); } else if (getLastVisiblePosition() == mTotalItemCount - 1 && (mFooterView.getBottomMargin() > 0 || deltaY < 0)) { // last item, already pulled up or want to pull up. updateFooterHeight(-deltaY / OFFSET_RADIO); } break; default: mLastY = -1; // reset if (getFirstVisiblePosition() == 0) { // invoke refresh if (mEnablePullRefresh && mHeaderView.getVisiableHeight() > mHeaderViewHeight) { mPullRefreshing = true; mHeaderView.setState(XListViewHeader.STATE_REFRESHING); if (mListViewListener != null) { mListViewListener.onRefresh(); } } resetHeaderHeight(); } else if (getLastVisiblePosition() == mTotalItemCount - 1) { // invoke load more. if (mEnablePullLoad && mFooterView.getBottomMargin() > PULL_LOAD_MORE_DELTA) { startLoadMore(); } resetFooterHeight(); } break; } return super.onTouchEvent(ev); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { if (mScrollBack == SCROLLBACK_HEADER) { mHeaderView.setVisiableHeight(mScroller.getCurrY()); } else { mFooterView.setBottomMargin(mScroller.getCurrY()); } postInvalidate(); invokeOnScrolling(); } super.computeScroll(); } @Override public void setOnScrollListener(OnScrollListener l) { mScrollListener = l; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (mScrollListener != null) { mScrollListener.onScrollStateChanged(view, scrollState); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // send to user's listener mTotalItemCount = totalItemCount; if (mScrollListener != null) { mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } } public void setXListViewListener(IXListViewListener l) { mListViewListener = l; } /** * you can listen ListView.OnScrollListener or this one. it will invoke * onXScrolling when header/footer scroll back. */ public interface OnXScrollListener extends OnScrollListener { public void onXScrolling(View view); } /** * implements this interface to get refresh/load more event. */ public interface IXListViewListener { public void onRefresh(); public void onLoadMore(); } }
2、 XListViewHeader的源码如下:
package com.everyone.android.widget; import android.content.Context; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.animation.Animation; import android.view.animation.RotateAnimation; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import com.everyone.android.R; public class XListViewHeader extends LinearLayout { private LinearLayout mContainer; private ImageView mArrowImageView; private ProgressBar mProgressBar; private TextView mHintTextView; private int mState = STATE_NORMAL; private Animation mRotateUpAnim; private Animation mRotateDownAnim; private final int ROTATE_ANIM_DURATION = 180; public final static int STATE_NORMAL = 0; public final static int STATE_READY = 1; public final static int STATE_REFRESHING = 2; public XListViewHeader(Context context) { super(context); initView(context); } /** * @param context * @param attrs */ public XListViewHeader(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } private void initView(Context context) { // 初始情况,设置下拉刷新view高度为0 LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LayoutParams.FILL_PARENT, 0); mContainer = (LinearLayout) LayoutInflater.from(context).inflate( R.layout.xlistview_header, null); addView(mContainer, lp); setGravity(Gravity.BOTTOM); mArrowImageView = (ImageView)findViewById(R.id.xlistview_header_arrow); mHintTextView = (TextView)findViewById(R.id.xlistview_header_hint_textview); mProgressBar = (ProgressBar)findViewById(R.id.xlistview_header_progressbar); mRotateUpAnim = new RotateAnimation(0.0f, -180.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION); mRotateUpAnim.setFillAfter(true); mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION); mRotateDownAnim.setFillAfter(true); } public void setState(int state) { if (state == mState) return ; if (state == STATE_REFRESHING) { // 显示进度 mArrowImageView.clearAnimation(); mArrowImageView.setVisibility(View.INVISIBLE); mProgressBar.setVisibility(View.VISIBLE); } else { // 显示箭头图片 mArrowImageView.setVisibility(View.VISIBLE); mProgressBar.setVisibility(View.INVISIBLE); } switch(state){ case STATE_NORMAL: if (mState == STATE_READY) { mArrowImageView.startAnimation(mRotateDownAnim); } if (mState == STATE_REFRESHING) { mArrowImageView.clearAnimation(); } mHintTextView.setText(R.string.xlistview_header_hint_normal); break; case STATE_READY: if (mState != STATE_READY) { mArrowImageView.clearAnimation(); mArrowImageView.startAnimation(mRotateUpAnim); mHintTextView.setText(R.string.xlistview_header_hint_ready); } break; case STATE_REFRESHING: mHintTextView.setText(R.string.xlistview_header_hint_loading); break; default: } mState = state; } public void setVisiableHeight(int height) { if (height < 0) height = 0; LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mContainer .getLayoutParams(); lp.height = height; mContainer.setLayoutParams(lp); } public int getVisiableHeight() { return mContainer.getHeight(); } }
3、 XListViewFooter的源码如下:
package com.everyone.android.widget; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; import com.everyone.android.R; public class XListViewFooter extends LinearLayout { public final static int STATE_NORMAL = 0; public final static int STATE_READY = 1; public final static int STATE_LOADING = 2; private Context mContext; private View mContentView; private View mProgressBar; private TextView mHintView; private TextView mLoadTextView; public XListViewFooter(Context context) { super(context); initView(context); } public XListViewFooter(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public void setState(int state) { mHintView.setVisibility(View.INVISIBLE); mProgressBar.setVisibility(View.INVISIBLE); mHintView.setVisibility(View.INVISIBLE); mLoadTextView.setVisibility(View.INVISIBLE); if (state == STATE_READY) { mHintView.setVisibility(View.VISIBLE); mHintView.setText(R.string.xlistview_footer_hint_ready); } else if (state == STATE_LOADING) { mProgressBar.setVisibility(View.VISIBLE); mLoadTextView.setVisibility(View.VISIBLE); } else { mHintView.setVisibility(View.VISIBLE); mHintView.setText(R.string.xlistview_footer_hint_normal); } } public void setBottomMargin(int height) { if (height < 0) return ; LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)mContentView.getLayoutParams(); lp.bottomMargin = height; mContentView.setLayoutParams(lp); } public int getBottomMargin() { LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)mContentView.getLayoutParams(); return lp.bottomMargin; } /** * normal status */ public void normal() { mHintView.setVisibility(View.VISIBLE); mProgressBar.setVisibility(View.GONE); mLoadTextView.setVisibility(View.GONE); } /** * loading status */ public void loading() { mHintView.setVisibility(View.GONE); mProgressBar.setVisibility(View.VISIBLE); mLoadTextView.setVisibility(View.VISIBLE); } /** * hide footer when disable pull load more */ public void hide() { LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)mContentView.getLayoutParams(); lp.height = 0; mContentView.setLayoutParams(lp); } /** * show footer */ public void show() { LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)mContentView.getLayoutParams(); lp.height = LayoutParams.WRAP_CONTENT; mContentView.setLayoutParams(lp); } private void initView(Context context) { mContext = context; LinearLayout moreView = (LinearLayout)LayoutInflater.from(mContext).inflate(R.layout.xlistview_footer, null); addView(moreView); moreView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); mContentView = moreView.findViewById(R.id.xlistview_footer_content); mProgressBar = moreView.findViewById(R.id.xlistview_footer_progressbar); mLoadTextView = (TextView)moreView.findViewById(R.id.xlistview_footer_load_textView); mHintView = (TextView)moreView.findViewById(R.id.xlistview_footer_hint_textview); } }
xlistview_header.xml文件源码:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="bottom" > <RelativeLayout android:id="@+id/xlistview_header_content" android:layout_width="fill_parent" android:layout_height="60dp" > <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center" android:orientation="vertical" android:id="@+id/xlistview_header_text"> <TextView android:id="@+id/xlistview_header_hint_textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/xlistview_header_hint_normal" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/xlistview_header_last_time" android:textSize="12sp" /> <TextView android:id="@+id/xlistview_header_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="12sp" /> </LinearLayout> </LinearLayout> <ImageView android:id="@+id/xlistview_header_arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@id/xlistview_header_text" android:layout_centerVertical="true" android:layout_marginLeft="-35dp" android:src="@drawable/xlistview_arrow" /> <ProgressBar android:id="@+id/xlistview_header_progressbar" android:layout_width="30dp" android:layout_height="30dp" android:layout_alignLeft="@id/xlistview_header_text" android:layout_centerVertical="true" android:layout_marginLeft="-40dp" android:visibility="invisible" /> </RelativeLayout> </LinearLayout>
xlistview_footer.xml的源码如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" > <RelativeLayout android:id="@+id/xlistview_footer_content" android:layout_width="fill_parent" android:layout_height="45dip" android:background="#FFFFFF" > <TextView android:id="@+id/xlistview_footer_hint_textview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="@string/xlistview_footer_hint_normal" /> <LinearLayout android:layout_width="fill_parent" android:layout_height="45dip" android:gravity="center_horizontal|center_vertical" android:orientation="horizontal" > <ProgressBar android:id="@+id/xlistview_footer_progressbar" android:layout_width="30dip" android:layout_height="30dip" /> <TextView android:id="@+id/xlistview_footer_load_textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="10dip" android:text="正在加载" android:textColor="#000000" android:textSize="15sp" /> </LinearLayout> </RelativeLayout> </LinearLayout>
strings.xml文件的源码如下:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">CopyEveryone</string> <string-array name="left_panel_group_names"> <item>常用</item> <item>更多</item> <item>操作</item> </string-array> <string-array name="left_panel_first_group_names"> <item>新鲜事</item> <item>消息</item> <item>聊天</item> <item>好友</item> <item>找人</item> </string-array> <string-array name="left_panel_second_group_names"> <item>附近</item> <item>公共主页</item> <item>热门分享</item> <item>应用与游戏</item> </string-array> <string-array name="left_panel_group_three_names"> <item>选项</item> <item>注销登录</item> </string-array> <string-array name="fresh_news_filter_list"> <item>新鲜事</item> <item>好友内容</item> <item>特别关注</item> <item>状态</item> <item>照片</item> <item>位置</item> <item>分享</item> <item>日志</item> </string-array> <string name="xlistview_header_hint_normal">下拉刷新</string> <string name="xlistview_header_hint_ready">松开刷新数据</string> <string name="xlistview_header_hint_loading">正在加载</string> <string name="xlistview_header_last_time">上次更新时间:</string> <string name="xlistview_footer_hint_normal">查看更多</string> <string name="xlistview_footer_hint_ready">松开载入更多</string> </resources>
开源组件XListView的demo的下载地址:https://github.com/Maxwin-z/XListView-Android (大家到这个网址去下载吧,我试过了没问题可以下载的。之前我上传的有网友说下载下来不全,我试图再次上传,几次上传都以失败告终。之前不全的资源也删不了,CSDN的上传资源貌似有问题,每次传到百分之XX就不动了,无语)
这一篇就聊到这里了,好久不写博客,手有些生疏了,讲的不到位的请大家谅解。前段时间公司的事比较多,就没有更新博客,后面我会继续更新的,谢谢大家的关注。