Android 高仿知乎日报 (上)

首先贴出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抽取了出来。 
看到这里,就会发现其实今天的内容就是对上篇文章的扩展,从个体扩展到通用。


你可能感兴趣的:(android,ImageLoader,知乎日报)