原创于:krelve.com
上一篇文章中完成了一个大致的界面,这次呢主要是完成主页面的展示。先做出一个最新消息的展示效果。
展示一下:
这就是要显示的效果,最上面是一个图片轮播控件,下面则是文章列表。
那好,让我们从头分析一下:根据滚动效果,可以看出这个整体是一个ListView,图片轮播控件则被当作它的headerView,下面则是各个文章。还需要注意的是今日热闻,需要在定义ListView的adapter时注意一下。
先贴上图片轮播控件的代码,这个效果很常见,所以我把这个控件抽取出来放到了github上,方便使用:Kanner
当然,这个控件最关键的是提供一种通用情况,像现在这种需要在轮播的图片上显示文字的情况,则需要再额外添加。
Kanner.java:
package krelve.app.kuaihu.view;
import android.content.Context;
import android.os.Handler;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.nostra13.universalimageloader.core.ImageLoader;
import java.util.ArrayList;
import java.util.List;
import krelve.app.kuaihu.Kpplication;
import krelve.app.kuaihu.R;
import krelve.app.kuaihu.model.Latest;
public class Kanner extends FrameLayout implements OnClickListener {
private List<Latest.TopStoriesEntity> topStoriesEntities;
private ImageLoader mImageLoader;
private List<View> views;
private Context context;
private ViewPager vp;
private boolean isAutoPlay;
private int currentItem;
private int delayTime;
private LinearLayout ll_dot;
private List<ImageView> iv_dots;
private Handler handler = new Handler();
private OnItemClickListener mItemClickListener;
public Kanner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mImageLoader = ImageLoader.getInstance();
this.context = context;
initView();
}
private void initView() {
views = new ArrayList<View>();
iv_dots = new ArrayList<ImageView>();
delayTime = 2000;
}
public Kanner(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Kanner(Context context) {
this(context, null);
}
public void setTopEntities(List<Latest.TopStoriesEntity> topEntities) {
this.topStoriesEntities = topEntities;
reset();
}
private void reset() {
views.clear();
initUI();
}
private void initUI() {
View view = LayoutInflater.from(context).inflate(
R.layout.kanner_layout, this, true);
vp = (ViewPager) view.findViewById(R.id.vp);
ll_dot = (LinearLayout) view.findViewById(R.id.ll_dot);
ll_dot.removeAllViews();
int len = topStoriesEntities.size();
for (int i = 0; i < len; i++) {
ImageView iv_dot = new ImageView(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
params.leftMargin = 5;
params.rightMargin = 5;
ll_dot.addView(iv_dot, params);
iv_dots.add(iv_dot);
}
for (int i = 0; i <= len + 1; i++) {
View fm = LayoutInflater.from(context).inflate(
R.layout.kanner_content_layout, null);
ImageView iv = (ImageView) fm.findViewById(R.id.iv_title);
TextView tv_title = (TextView) fm.findViewById(R.id.tv_title);
iv.setScaleType(ScaleType.CENTER_CROP);
iv.setBackgroundResource(R.drawable.loading1);
if (i == 0) {
mImageLoader.displayImage(topStoriesEntities.get(len - 1).getImage(), iv);
tv_title.setText(topStoriesEntities.get(len - 1).getTitle());
} else if (i == len + 1) {
mImageLoader.displayImage(topStoriesEntities.get(0).getImage(), iv);
tv_title.setText(topStoriesEntities.get(0).getTitle());
} else {
mImageLoader.displayImage(topStoriesEntities.get(i - 1).getImage(), iv);
tv_title.setText(topStoriesEntities.get(i - 1).getTitle());
}
fm.setOnClickListener(this);
views.add(fm);
}
vp.setAdapter(new MyPagerAdapter());
vp.setFocusable(true);
vp.setCurrentItem(1);
currentItem = 1;
vp.addOnPageChangeListener(new MyOnPageChangeListener());
startPlay();
}
private void startPlay() {
isAutoPlay = true;
handler.postDelayed(task, 3000);
}
private final Runnable task = new Runnable() {
@Override
public void run() {
if (isAutoPlay) {
currentItem = currentItem % (topStoriesEntities.size() + 1) + 1;
if (currentItem == 1) {
vp.setCurrentItem(currentItem, false);
handler.post(task);
} else {
vp.setCurrentItem(currentItem);
handler.postDelayed(task, 5000);
}
} else {
handler.postDelayed(task, 5000);
}
}
};
class MyPagerAdapter extends PagerAdapter {
@Override
public int getCount() {
return views.size();
}
@Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == arg1;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
container.addView(views.get(position));
return views.get(position);
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
}
class MyOnPageChangeListener implements OnPageChangeListener {
@Override
public void onPageScrollStateChanged(int arg0) {
switch (arg0) {
case 1:
isAutoPlay = false;
break;
case 2:
isAutoPlay = true;
break;
case 0:
if (vp.getCurrentItem() == 0) {
vp.setCurrentItem(topStoriesEntities.size(), false);
} else if (vp.getCurrentItem() == topStoriesEntities.size() + 1) {
vp.setCurrentItem(1, false);
}
currentItem = vp.getCurrentItem();
isAutoPlay = true;
break;
}
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
@Override
public void onPageSelected(int arg0) {
for (int i = 0; i < iv_dots.size(); i++) {
if (i == arg0 - 1) {
iv_dots.get(i).setImageResource(R.drawable.dot_focus);
} else {
iv_dots.get(i).setImageResource(R.drawable.dot_blur);
}
}
}
}
public void setOnItemClickListener(OnItemClickListener mItemClickListener) {
this.mItemClickListener = mItemClickListener;
}
public interface OnItemClickListener {
public void click(Latest.TopStoriesEntity entity);
}
@Override
public void onClick(View v) {
if (mItemClickListener != null) {
Latest.TopStoriesEntity entity = topStoriesEntities.get(vp.getCurrentItem() - 1);
mItemClickListener.click(entity);
}
}
}
还有布局文件kanner_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<android.support.v4.view.ViewPager
android:id="@+id/vp"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:id="@+id/ll_dot"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:gravity="right"
android:orientation="horizontal"
android:padding="8dp" >
</LinearLayout>
</merge>
以及要显示的内容的布局文件kanner_content_layout:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="@+id/iv_title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginBottom="25dp"
android:padding="10dp"
android:text="标题"
android:textColor="@android:color/white"
android:textSize="20sp" />
</FrameLayout>
那接下来就是如何设置这个控件要显示的数据了:
MainFragment.java:
package krelve.app.kuaihu.fragment;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListView;
import com.google.gson.Gson;
import com.loopj.android.http.TextHttpResponseHandler;
import org.apache.http.Header;
import java.util.ArrayList;
import java.util.List;
import krelve.app.kuaihu.R;
import krelve.app.kuaihu.activity.MainActivity;
import krelve.app.kuaihu.adapter.NewsItemAdapter;
import krelve.app.kuaihu.model.Latest;
import krelve.app.kuaihu.util.Constant;
import krelve.app.kuaihu.util.HttpUtils;
import krelve.app.kuaihu.view.Kanner;
/**
* Created by wwjun.wang on 2015/8/12.
*/
public class MainFragment extends BaseFragment {
private ListView lv_news;
private List<Latest> items;
private Latest latest;
private Kanner kanner;
private Handler handler = new Handler();
@Override
protected View initView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.main_news_layout, container, false);
lv_news = (ListView) view.findViewById(R.id.lv_news);
View header = inflater.inflate(R.layout.kanner, lv_news, false);
kanner = (Kanner) header.findViewById(R.id.kanner);
kanner.setOnItemClickListener(new Kanner.OnItemClickListener() {
@Override
public void click(Latest.TopStoriesEntity entity) {
}
});
lv_news.addHeaderView(header);
lv_news.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (lv_news != null && lv_news.getChildCount() > 0) {
boolean enable = (firstVisibleItem == 0) && (view.getChildAt(firstVisibleItem).getTop() == 0);
((MainActivity) mActivity).setSwipeRefreshEnable(enable);
}
}
});
return view;
}
@Override
protected void initData() {
super.initData();
HttpUtils.get(Constant.LATESTNEWS, new TextHttpResponseHandler() {
@Override
public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) {
}
@Override
public void onSuccess(int statusCode, Header[] headers, String responseString) {
Gson gson = new Gson();
latest = gson.fromJson(responseString, Latest.class);
kanner.setTopEntities(latest.getTop_stories());
handler.post(new Runnable() {
@Override
public void run() {
List<Latest.StoriesEntity> storiesEntities = latest.getStories();
Latest.StoriesEntity topic = new Latest.StoriesEntity();
topic.setType(Constant.TOPIC);
topic.setTitle("今日热闻");
storiesEntities.add(0, topic);
lv_news.setAdapter(new NewsItemAdapter(mActivity, storiesEntities));
}
});
}
});
}
}
可以看到,在解析数据的时候,用到了Gson这个开源库,直接将json格式的数据映射到实体bean中。
Latest.java:
package krelve.app.kuaihu.model;
import java.util.List;
/**
* Created by wwjun.wang on 2015/8/12.
*/
public class Latest {
/**
* top_stories : [{"id":7048089,"title":"发生类似天津爆炸事故时,该如何自救?","ga_prefix":"081309","image":"http://pic4.zhimg.com/494dafbd64c141fd023d4e58b3343fcb.jpg","type":0},{"id":7047383,"title":"每卖一辆车亏 4000 美元,这事儿跟「iPhone 成本仅几百元」挺像","ga_prefix":"081307","image":"http://pic1.zhimg.com/40e0f21292df0e8512385f191e71ad14.jpg","type":0},{"id":7047795,"title":"央视说要干预男男性行为,具体是怎么干预法?","ga_prefix":"081310","image":"http://pic4.zhimg.com/89f0bca7d4ccf70bd747f3675adc18eb.jpg","type":0},{"id":7047071,"title":"美国人最爱买的车第一名是它,第二名是它,第三名,还是它\u2026\u2026","ga_prefix":"081307","image":"http://pic1.zhimg.com/9c00c482251e82fa8e0b957fa9ceb334.jpg","type":0},{"id":7046751,"title":"今晚的修破斯哒是 · 小李子","ga_prefix":"081219","image":"http://pic3.zhimg.com/bc5f63634d9c9832da8593ac64ebb7d6.jpg","type":0}]
* stories : [{"id":7047795,"title":"央视说要干预男男性行为,具体是怎么干预法?","ga_prefix":"081310","images":["http://pic3.zhimg.com/fe27abc8f094510f2d3b4f3706108b56.jpg"],"type":0},{"id":7048089,"title":"发生类似天津爆炸事故时,该如何自救?","ga_prefix":"081309","images":["http://pic1.zhimg.com/eabb48a57948dc405429d0c0185c7950.jpg"],"type":0},{"id":7047188,"title":"分析了一下,发现这几个中国城市不光物价高,而且收入低","ga_prefix":"081308","images":["http://pic2.zhimg.com/7ce0dfe918b11069bc857421876e6609.jpg"],"type":0},{"id":7047383,"title":"每卖一辆车亏 4000 美元,这事儿跟「iPhone 成本仅几百元」挺像","ga_prefix":"081307","images":["http://pic2.zhimg.com/b8319323c8f8e3ec0ccc80d4745305a9.jpg"],"type":0},{"id":7047071,"title":"美国人最爱买的车第一名是它,第二名是它,第三名,还是它\u2026\u2026","ga_prefix":"081307","images":["http://pic2.zhimg.com/822b9ce452e8d48e6d32b83d4c1ea6e9.jpg"],"type":0},{"id":7047484,"title":"理论上就业率对工资很重要,但是在中国没这么回事","ga_prefix":"081307","images":["http://pic4.zhimg.com/d08952ed50050efdcee33203a4225ba3.jpg"],"type":0},{"id":7047181,"title":"瞎扯 · 如何正确地吐槽","ga_prefix":"081306","images":["http://pic4.zhimg.com/1dd6304067619318034671af9cf26803.jpg"],"type":0}]
* date : 20150813
*/
private List<TopStoriesEntity> top_stories;
private List<StoriesEntity> stories;
private String date;
public void setTop_stories(List<TopStoriesEntity> top_stories) {
this.top_stories = top_stories;
}
public void setStories(List<StoriesEntity> stories) {
this.stories = stories;
}
public void setDate(String date) {
this.date = date;
}
public List<TopStoriesEntity> getTop_stories() {
return top_stories;
}
public List<StoriesEntity> getStories() {
return stories;
}
public String getDate() {
return date;
}
public static class TopStoriesEntity {
/**
* id : 7048089
* title : 发生类似天津爆炸事故时,该如何自救?
* ga_prefix : 081309
* image : http://pic4.zhimg.com/494dafbd64c141fd023d4e58b3343fcb.jpg
* type : 0
*/
private int id;
private String title;
private String ga_prefix;
private String image;
private int type;
public void setId(int id) {
this.id = id;
}
public void setTitle(String title) {
this.title = title;
}
public void setGa_prefix(String ga_prefix) {
this.ga_prefix = ga_prefix;
}
public void setImage(String image) {
this.image = image;
}
public void setType(int type) {
this.type = type;
}
public int getId() {
return id;
}
public String getTitle() {
return title;
}
public String getGa_prefix() {
return ga_prefix;
}
public String getImage() {
return image;
}
public int getType() {
return type;
}
@Override
public String toString() {
return "TopStoriesEntity{" +
"id=" + id +
", title='" + title + '\'' +
", ga_prefix='" + ga_prefix + '\'' +
", image='" + image + '\'' +
", type=" + type +
'}';
}
}
public static class StoriesEntity {
/**
* id : 7047795
* title : 央视说要干预男男性行为,具体是怎么干预法?
* ga_prefix : 081310
* images : ["http://pic3.zhimg.com/fe27abc8f094510f2d3b4f3706108b56.jpg"]
* type : 0
*/
private int id;
private String title;
private String ga_prefix;
private List<String> images;
private int type;
public void setId(int id) {
this.id = id;
}
public void setTitle(String title) {
this.title = title;
}
public void setGa_prefix(String ga_prefix) {
this.ga_prefix = ga_prefix;
}
public void setImages(List<String> images) {
this.images = images;
}
public void setType(int type) {
this.type = type;
}
public int getId() {
return id;
}
public String getTitle() {
return title;
}
public String getGa_prefix() {
return ga_prefix;
}
public List<String> getImages() {
return images;
}
public int getType() {
return type;
}
@Override
public String toString() {
return "StoriesEntity{" +
"id=" + id +
", title='" + title + '\'' +
", ga_prefix='" + ga_prefix + '\'' +
", images=" + images +
", type=" + type +
'}';
}
}
@Override
public String toString() {
return "Latest{" +
"top_stories=" + top_stories +
", stories=" + stories +
", date='" + date + '\'' +
'}';
}
}
看到这个实体bean的时候是不是感觉很复杂,写的时候很不好写?
不要担心,我们有GsonFormat这个插件,将需要解析的json数据输入,便可以自动生成对应的实体bean,简直不能更爽。
还有一个关键点是冲突的解决,由于使用了SwipeRefreshListener这个下拉刷新控件,而ListView又包含在其中,那再下拉的时候如果不处理,必然会起冲突。
其实解决起来很简单,我们只需要让下拉刷新的操作在ListView被滑动到最顶部的时候进行就可以了:
lv_news.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (lv_news != null && lv_news.getChildCount() > 0) {
boolean enable = (firstVisibleItem == 0) && (view.getChildAt(firstVisibleItem).getTop() == 0);
((MainActivity) mActivity).setSwipeRefreshEnable(enable);
}
}
});
现在大概就实现了今日热闻的展示,那接下来还要实现下拉刷新和自动加载更多,以及其他内容页的展示,今天就写这么多,有关ListView中每一条内容的展示布局等可以在源码中看,最新内容已同步到github:高仿知乎日报
另外,欢迎大家到我的个人博客——krelve.com访问与留言