首先贴出GitHub网址:

https://github.com/iKrelve/KuaiHu

Part1

1.首先编写Application,由于使用到了UIL框架,所以在Application中初始化它。

package krelve.app.kuaihu; import android.app.Application; import android.content.Context; import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache; import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator; import com.nostra13.universalimageloader.cache.memory.impl.LruMemoryCache; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; import com.nostra13.universalimageloader.core.assist.QueueProcessingType; import com.nostra13.universalimageloader.utils.StorageUtils; import java.io.File; /** * Created by wwjun.wang on 2015/8/11. */ public class Kpplication extends Application { @Override public void onCreate() { super.onCreate(); initImageLoader(getApplicationContext()); } private void initImageLoader(Context context) { File cacheDir = StorageUtils.getCacheDirectory(context); ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder( context).threadPoolSize(3) .threadPriority(Thread.NORM_PRIORITY - 2) .memoryCache(new LruMemoryCache(2 * 1024 * 1024)) .denyCacheImageMultipleSizesInMemory() .diskCacheFileNameGenerator(new Md5FileNameGenerator()) .tasksProcessingOrder(QueueProcessingType.LIFO) .diskCache(new UnlimitedDiskCache(cacheDir)).writeDebugLogs() .build(); ImageLoader.getInstance().init(config); } } 

2.打开知乎日报,发现会有一个启动页,包含一个放大图片的动画,实现起来很简单:
简单的布局文件

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center_vertical"> <ImageView  android:id="@+id/iv_start" android:layout_width="match_parent" android:layout_height="match_parent" android:adjustViewBounds="true" android:scaleType="fitXY" /> </RelativeLayout>

界面代码

package krelve.app.kuaihu.activity; import android.app.Activity; import android.content.Intent; import android.graphics.BitmapFactory; import android.os.Bundle; import android.view.Window; import android.view.animation.Animation; import android.view.animation.ScaleAnimation; import android.widget.ImageView; import com.loopj.android.http.AsyncHttpResponseHandler; import com.loopj.android.http.BinaryHttpResponseHandler; import org.apache.http.Header; import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import krelve.app.kuaihu.R; import krelve.app.kuaihu.util.Constant; import krelve.app.kuaihu.util.HttpUtils; /** * Created by wwjun.wang on 2015/8/11. */ public class SplashActivity extends Activity { private ImageView iv_start; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.splash); iv_start = (ImageView) findViewById(R.id.iv_start); initImage(); } private void initImage() { File dir = getFilesDir(); final File imgFile = new File(dir, "start.jpg"); if (imgFile.exists()) { iv_start.setImageBitmap(BitmapFactory.decodeFile(imgFile.getAbsolutePath())); } else { iv_start.setImageResource(R.mipmap.start); } final ScaleAnimation scaleAnim = new ScaleAnimation(1.0f, 1.2f, 1.0f, 1.2f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); scaleAnim.setFillAfter(true); scaleAnim.setDuration(3000); scaleAnim.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { HttpUtils.get(Constant.START, new AsyncHttpResponseHandler() { @Override public void onSuccess(int i, Header[] headers, byte[] bytes) { try { JSONObject jsonObject = new JSONObject(new String(bytes)); String url = jsonObject.getString("img"); HttpUtils.get(url, new BinaryHttpResponseHandler() { @Override public void onSuccess(int i, Header[] headers, byte[] bytes) { saveImage(imgFile, bytes); startActivity(); } @Override public void onFailure(int i, Header[] headers, byte[] bytes, Throwable throwable) { startActivity(); } }); } catch (JSONException e) { e.printStackTrace(); } } @Override public void onFailure(int i, Header[] headers, byte[] bytes, Throwable throwable) { startActivity(); } }); } @Override public void onAnimationRepeat(Animation animation) { } }); iv_start.startAnimation(scaleAnim); } private void startActivity() { Intent intent = new Intent(SplashActivity.this, MainActivity.class); startActivity(intent); overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); finish(); } public void saveImage(File file, byte[] bytes) { try { file.delete(); FileOutputStream fos = new FileOutputStream(file); fos.write(bytes); fos.flush(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } } 

在知乎日报的接口中,我们会看到这样一个接口:
http://news-at.zhihu.com/api/4/start-image/1080*1776
用来获取启动界面的图像,所以在启动时,要去获取最新的启动图像。
这里用到了android-http-async框架和UIM框架,在Module的build.gradle文件中添加:

 compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.4' compile 'com.loopj.android:android-async-http:1.4.8'

还要记得在manifest文件中加权限:

 <uses-permission android:name="android.permission.INTERNET"></uses-permission> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission> <uses-permission android:name="android.permission.write_external_storage"></uses-permission>

这样基本就完成了一个启动页面,很简单。
3.主界面布局:
先来一个效果图:

可以看到,用到了Toolbar和DrawerLayout还有SwipeRefreshLayout。
布局文件

<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/drawerlayout" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/sr" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?attr/colorPrimaryDark" android:theme="@style/MyActionBar" /> <FrameLayout android:id="@+id/fl_content" android:layout_width="match_parent" android:layout_height="match_parent"></FrameLayout> </LinearLayout> </android.support.v4.widget.SwipeRefreshLayout> <fragment android:name="krelve.app.kuaihu.fragment.MenuFragment" android:layout_width="300dp" android:layout_height="match_parent" android:layout_gravity="left" /> </android.support.v4.widget.DrawerLayout>

可以看到Toolbar的位置在DrawerLayout的里面,如果想要侧滑的时候侧滑菜单显示在Toolbar的下面,只需让Toolbar的位置在DrawerLayout外面就行。
这里需要注意的是Toolbar的样式,如果仔细看了知乎日报的Toolbar就会发现它用的应该是Dark类型的主题,因为这个Toolbar除了背景其它地方都是白色的,那好,我们直接给Toolbar设置android:theme=ThemeOverlay.AppCompat.ActionBar
然后问题就来了,再点开右侧overflow,会发现弹出的菜单背景是黑色的。
这可不行,清新的风格瞬间被毁,于是在style.xml中定义自己的style:

 <style name="MyActionBar" parent="ThemeOverlay.AppCompat.ActionBar"> <!--<item name="android:actionOverflowButtonStyle">@style/MyOverflowButton</item>--> <item name="android:textColor">@android:color/black</item> </style> 

给我们的Toolbar引用这个style就达到了目的。
那就剩下侧滑菜单的编写了,本来是想用NavigationView来实现的,但是效果不怎么理想,还是自己写吧。
布局文件

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="360dp" android:layout_height="match_parent" android:clickable="true" android:orientation="vertical"> <LinearLayout  android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/holo_blue_dark" android:orientation="vertical" android:paddingBottom="10dp"> <LinearLayout  android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="15dp" android:layout_marginTop="15dp" android:orientation="horizontal"> <ImageView  android:layout_width="36dp" android:layout_height="36dp" android:background="@drawable/ic_account_circle_white_24dp" /> <TextView  android:layout_width="wrap_content" android:layout_height="36dp" android:layout_marginLeft="10dp" android:gravity="center_vertical" android:text="请登录" android:textColor="@android:color/white" android:textSize="18sp" /> </LinearLayout> <LinearLayout  android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="25dp" android:orientation="horizontal"> <TextView  android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_weight="1" android:drawableLeft="@drawable/ic_star_white_24dp" android:gravity="center" android:text="我的收藏" android:textColor="@android:color/white" android:textSize="15sp" /> <TextView  android:id="@+id/tv_download" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="20dp" android:layout_weight="1" android:drawableLeft="@drawable/ic_file_download_white_24dp" android:gravity="center" android:text="离线下载" android:textColor="@android:color/white" android:textSize="15sp" /> </LinearLayout> </LinearLayout> <TextView  android:id="@+id/tv_main" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#FFF0F0F0" android:paddingBottom="10dp" android:paddingLeft="80dp" android:paddingTop="10dp" android:text="首页" android:textColor="@android:color/holo_blue_dark" android:textSize="18sp" /> <ListView  android:id="@+id/lv_item" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" android:divider="@android:color/transparent" android:scrollbars="none"></ListView> </LinearLayout>

关键点是ListView,这里要显示的数据我们要通过接口http://news-at.zhihu.com/api/4/themes来获取,为了简化网络操作,对android-http-async进行了及其简单的封装:

package krelve.app.kuaihu.util; import com.loopj.android.http.AsyncHttpClient; import com.loopj.android.http.ResponseHandlerInterface; /** * Created by wwjun.wang on 2015/8/11. */ public class HttpUtils { private static AsyncHttpClient client = new AsyncHttpClient(); public static void get(String url, ResponseHandlerInterface responseHandler) { client.get(Constant.BASEURL + url, responseHandler); } } 

获取到的都是Json格式的字串,由于现在遇到的json格式比较简单,所以直接用android中自带的json解析库来解析,在后面会用到Gson直接向bean中映射。
贴上整个Fragment的代码:

package krelve.app.kuaihu.fragment; import android.os.Bundle; import android.os.Handler; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; import com.loopj.android.http.JsonHttpResponseHandler; import org.apache.http.Header; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; import krelve.app.kuaihu.R; import krelve.app.kuaihu.model.NewsListItem; import krelve.app.kuaihu.util.Constant; import krelve.app.kuaihu.util.HttpUtils; public class MenuFragment extends Fragment implements OnClickListener { private ListView lv_item; private TextView tv_download, tv_main; // private static String[] ITEMS = { "日常心理学", "用户推荐日报", "电影日报", "不许无聊", // "设计日报", "大公司日报", "财经日报", "互联网安全", "开始游戏", "音乐日报", "动漫日报", "体育日报" }; private List<NewsListItem> items; private Handler handler = new Handler(); @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.menu, container, false); tv_download = (TextView) view.findViewById(R.id.tv_download); tv_download.setOnClickListener(this); tv_main = (TextView) view.findViewById(R.id.tv_main); tv_main.setOnClickListener(this); lv_item = (ListView) view.findViewById(R.id.lv_item); getItems(); return view; } private void getItems() { items = new ArrayList<NewsListItem>(); HttpUtils.get(Constant.THEMES, new JsonHttpResponseHandler() { @Override public void onSuccess(int statusCode, Header[] headers, JSONObject response) { super.onSuccess(statusCode, headers, response); try { JSONArray itemsArray = response.getJSONArray("others"); for (int i = 0; i < itemsArray.length(); i++) { NewsListItem newsListItem = new NewsListItem(); JSONObject itemObject = itemsArray.getJSONObject(i); newsListItem.setTitle(itemObject.getString("name")); newsListItem.setId(itemObject.getString("id")); items.add(newsListItem); } handler.post(new Runnable() { @Override public void run() { lv_item.setAdapter(new NewsTypeAdapter()); } }); } catch (JSONException e) { e.printStackTrace(); } } }); } public class NewsTypeAdapter extends BaseAdapter { @Override public int getCount() { return items.size(); } @Override public Object getItem(int position) { return items.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = LayoutInflater.from(getActivity()).inflate( R.layout.menu_item, parent, false); } TextView tv_item = (TextView) convertView .findViewById(R.id.tv_item); tv_item.setText(items.get(position).getTitle()); return convertView; } } @Override public void onClick(View v) { } } } 

NewsListItem.java:

package krelve.app.kuaihu.model; public class NewsListItem { private String title; private String id; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getId() { return id; } public void setId(String id) { this.id = id; } } 

menu_item.xml:

<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/tv_item" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:paddingBottom="10dp" android:paddingLeft="15dp" android:paddingTop="10dp" android:text="新闻条目" android:textColor="#FF000000" android:textSize="16dp" /> 


Part2

那好,让我们从头分析一下:根据滚动效果,可以看出这个整体是一个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); } } });


Part3

在写各类文章的界面前,先把今日热闻的下拉刷新逻辑和自动加载更多的功能完成。
还记得之前处理swiperefreshlayout与listview的滑动冲突吗?就是在那里添加:

 @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); if (firstVisibleItem + visibleItemCount == totalItemCount && !isLoading) { loadMore(Constant.BEFORE + date); } } }

loadMore方法:

private void loadMore(final String url) { isLoading = true; HttpUtils.get(url, 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(); before = gson.fromJson(responseString, Before.class); date = before.getDate(); handler.post(new Runnable() { @Override public void run() { List<StoriesEntity> storiesEntities = before.getStories(); StoriesEntity topic = new StoriesEntity(); topic.setType(Constant.TOPIC); topic.setTitle(convertDate(date)); storiesEntities.add(0, topic); mAdapter.addList(storiesEntities); isLoading = false; } }); } }); }

还有自动刷新的逻辑实现(目前只实现了今日热闻的刷新):

 sr.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { replaceFragment(); sr.setRefreshing(false); } }); public void replaceFragment() { if (curId.equals("latest")) { getSupportFragmentManager().beginTransaction().setCustomAnimations(R.anim.slide_in_from_right, R.anim.slide_out_to_left) .replace(R.id.fl_content, new MainFragment(), "latest").commit(); } else { } }

然后就是今天的重点了,首先去实现侧滑菜单栏的点击事件:

 lv_item.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { getFragmentManager() .beginTransaction().setCustomAnimations(R.anim.slide_in_from_right, R.anim.slide_out_to_left) .replace( R.id.fl_content, new NewsFragment(items.get(position) .getId()), "news").commit(); ((MainActivity) mActivity).setCurId(items.get(position).getId()); ((MainActivity) mActivity).closeMenu(); } });

其实就是按照点击的position来获取对应的id,然后发出请求,动态的用Fragment来展示。
这才是主角:

package krelve.app.kuaihu.fragment; import android.annotation.SuppressLint; import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import com.google.gson.Gson; import com.loopj.android.http.TextHttpResponseHandler; import com.nostra13.universalimageloader.core.ImageLoader; import org.apache.http.Header; import java.util.ArrayList; import krelve.app.kuaihu.R; import krelve.app.kuaihu.activity.MainActivity; import krelve.app.kuaihu.adapter.NewsItemAdapter; import krelve.app.kuaihu.model.News; import krelve.app.kuaihu.util.Constant; import krelve.app.kuaihu.util.HttpUtils; /** * Created by wwjun.wang on 2015/8/14. */ @SuppressLint("ValidFragment") public class NewsFragment extends BaseFragment { private ImageLoader mImageLoader; private ListView lv_news; private ImageView iv_title; private TextView tv_title; private String urlId; private News news; public NewsFragment(String id) { urlId = id; } @Override protected View initView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.news_layout, container, false); mImageLoader = ImageLoader.getInstance(); lv_news = (ListView) view.findViewById(R.id.lv_news); View header = LayoutInflater.from(mActivity).inflate( R.layout.news_header, lv_news, false); iv_title = (ImageView) header.findViewById(R.id.iv_title); tv_title = (TextView) header.findViewById(R.id.tv_title); lv_news.addHeaderView(header); // lv_news.setOnItemClickListener(new AdapterView.OnItemClickListener() { // // @Override // public void onItemClick(AdapterView<?> parent, View view, // int position, long id) { // NewsItem newsItem = (NewsItem) parent.getAdapter().getItem( // position); // Intent intent = new Intent(getActivity(), // ThemeNewsContentActivity.class); // intent.putExtra("id", newsItem.getId()); // intent.putExtra("title", newsItem.getTitle()); // startActivity(intent); // } // }); 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.THEMENEWS + urlId, 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(); news = gson.fromJson(responseString, News.class); tv_title.setText(news.getDescription()); mImageLoader.displayImage(news.getImage(), iv_title); lv_news.setAdapter(new NewsItemAdapter(mActivity, news.getStories())); } }); } } 

在完成了今日热闻后,对上面的代码肯定不陌生,因为它们有着惊人的相似,上面用到的所有bean都是用GsonFormat自动生成的,不过由于有很多重复的地方,所以我把StoriesEntity抽取了出来。
看到这里,就会发现其实今天的内容就是对上篇文章的扩展,从个体扩展到通用。


更多相关文章

  1. Android之Hello WebView
  2. android控件之spinner (下拉列表)
  3. fragment android
  4. android 设置控件 圆角
  5. android 状态选择器
  6. onTouch事件传递机制
  7. 使用MAT查看Android内存泄露
  8. Android(安卓)AlertDialog有EditText无法弹出输入法的解决
  9. MaterialDesign中控件属性表

随机推荐

  1. Android上下左右手势滑动事件处理
  2. android footer view
  3. Android开发指南(43) —— Location and
  4. 配置android的adb环境变量
  5. [原]Android上GTalk以及Push机制的XMPP数
  6. Android断点续传学习
  7. Android中Shape Drawable在xml中的使用
  8. android 事件模型
  9. ios 开发之基础控件
  10. Android(安卓)开发环境安装