原文出处：小巫CSDN博客客户端开发教程
作者：巫文杰

本系列文章经作者授权在看云整理发布，未经作者允许，请勿转载！

小巫CSDN博客客户端开发教程

小巫CSDN博客客户端，CSDN博主IT_xiao小巫的一个上线作品，喜欢分享的小巫制作以下教程解密这个客户端的开发实现，集成友盟社会化组件、自动更新、小米广告，一系列干活，实战型项目，初学者福音。

Android应用开发-小巫CSDN博客客户端开发开篇

2014年9月8日 八月十五 祝各位中秋节快乐

小巫断断续续花了几个星期的时间开发了这么一款应用——小巫CSDN博客，属于私人定制的这样的一款应用，整个客户端的数据全部来自本人博客，是通过爬取本人博客地址html页面，然后解析html把数据提取出来，整个客户端的技术难点主要是如何对html界面进行分析和使用Jsoup对html代码进行解析。目前本人的这款应用已经开发出来了，近段时间会提交应用商店进行审核，不久大家就可以看到这么一款逼格满满的客户端上线了。

本篇博客是关于这款应用的开发的起始篇，主要简单介绍一下整个项目的概况，整体大纲如下：

1. 项目起因

2. 项目效果展示

3. 项目文档结构和依赖库说明

4. 项目功能简介

5. 系列博客分享后期计划

一、项目起因

笔者开发这么一款客户端也是兴趣使然，因为CSDN官方并没有一款可供笔者实用的客户端，笔者就想着如何来开发一款客户端让自己方便在手机上查看博文，寻找了一圈，后来在CSDN博客上发现了一位大神所开发的CSDN资讯的客户端，并研究了其里面的实现。本人博客的主界面效果跟资讯客户端类似，只是进行了相关的调整，使其适应笔者所需要的效果。这里还得感谢那位大神的开源，我才有机会去开发关于博客的客户端，因为通过研究它的使用技术，让我有了思路去分析博客html代码，这样才有了这款应用的诞生。开源能促使人进步，所以小巫最后会把整个项目无私开源出来，让大家有一个很好的参考去开发这样的客户端。

这里要说一下CSDN开放平台，因为这个客户端属于私人定制的，所有的数据只来自本人博客，如果想开发适合大众使用的，唯有通过调用CSDN为开发者提供的接口，这里很遗憾的告诉大家，笔者尝试过与CSDN技术沟通，但并未得到满意的答复，CSDN开放平台目前暂时不能提供接口让我们使用，所以只能等待了。

二、项目效果展示

没图没真相，大家自然是当我凭口说空话，下面是小巫CSDN博客的界面效果截图：

主界面

侧边栏

博文详细内容

博文评论列表

三、项目文档结构与依赖库介绍

com.xiaowu.blogclient ——项目包名，存放界面相关代码

com.xiaowu.blogclient.adapter——存放内容适配器代码

com.xiaowu.blogclient.model——存放数据模型定义代码

com.xiaowu.blogclient.net——存放网络操作代码

com.xiaowu.blogclient.umeng——存放友盟组件相关代码

com.xiaowu.blogclient.util——存放工具类代码

com.xiaowu.blogclient.view——存放自定义控件代码

依赖库

这个截图展示的是，笔者项目使用的SDK版本和所有依赖库项目，这里分别介绍一下

gesture-imageview——可以手势操作的ImageView

imageloadlibrary——异步加载网络图片的库

viewPagerlibrary——视图页面切换的库

XListView——下拉刷新和上拉刷新的库

slidingmenu_library——侧滑菜单的库

这些库都是需要事先Import到工作空间，然后通过addLibrary关联到项目中去。

四、项目功能简介

本人这个客户端主要包含以下几个功能：

1. 分类获取博主博文列表

2. 查看博文详细内容

3. 查看博文评论列表

4. 软件自动更新

5. 社会化组件分享

6. 广告条、插屏广告

自然最重要的还是最后那一项功能，各位开发者幸幸苦苦开发一款应用自然想获取一定回报，笔者后面会以博文形式详细给大家介绍如何集成广告，然后获取一定的收益。

五、系列博客分享后期计划

笔者为了让初学者或者想更加清晰的了解整个客户端的开发细节，会发布一系列博文来介绍相关内容，比如如何分析和解析html代码，如何集成第三方组件，发布广告获取收益等等。一系列的干活，我想都是开发者比较想要看到的内容，笔者牺牲自己空余时间来做这样一件事情，是很高兴得到大家的关注和支持，为了不愧对大家的关注，为了传播真诚、分享、共赢的信念，做这些事情就是值得的，价值的体现才是创造的原动力。

最后项目已经开源，各位可以到笔者的github下载：https://github.com/devilWwj/CSDN-Blog-Client

由于本人的能力有限，项目可能有很多没有完善的地方，或者存在bug，后期笔者会继续完善和更新版本，谢谢大家。

Android应用开发-小巫CSDN博客客户端UI篇

上一篇是给童鞋们介绍整个项目的概况，从这篇博文开始，后续也会详细介绍整个客户端的开发，但不会贴很多代码，我会贴核心代码然后提供实现思路，想看里面更详细的代码的可以到我的资源页下载源码进行查看，之前上传到github的少了些jar包，所以我在csdn下载频道也上传了一份，地址：http://download.csdn.net/detail/wwj_748/7912513。

整个客户端的开始，自然是需要搭建一个承载我们数据的框架，我这里所说的是UI框架，我们需要事先设计好如何展示我们的内容，有哪些页面，每个页面之间的跳转是怎样的，我们把这些想好之后就可以进行UI界面的设计和实现了。Android中提供了两种实现布局的方法，一种是XML，一种是以代码，前者实现比较容易实现，只需要开发者知道如何布局控件就行，后者需要开发者有较好的灵活性和逻辑。如果不是实现动态布局的话，我们一般会以XML的形式实现布局。

小巫的这个客户端界面不算复杂，界面也算比较简洁。

（图1-启动页）

（图2-博文列表）

（图3-侧边栏）

（图4-博文详细内容）

（图5-博文评论列表）

以上给大家展示的是小巫CSDN博客客户端的主要界面效果，下面来讲解如何布局这样的界面：

启动界面布局

/BlogClient/res/layout/splash.xml

<?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:background="@drawable/splash" >

</RelativeLayout>

说明：启动界面就只是简单一个布局，设置背景图片即可。

主布局－页面切换

/BlogClient/res/layout/main_tab.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/wangyibg2"
android:orientation="vertical" >

<!-- 包含头部 -->
<include layout="@layout/main_head" />

<!-- 页面指示器 -->
<com.viewpagerindicator.TabPageIndicator
android:id="@+id/indicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/transparentblue" />

<!-- 页面切换器 -->
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />

</LinearLayout>

说明：这里用到了include标签，这个标签是用来包含布局模块的，可以减少代码冗余，其他界面想使用同样的布局的时候就不用重复写代码了，可读性也比较好。TabPageIndicator是自定义控件，项目中需要引入viewPagerlibrary这个库，它是与ViewPager配套使用，可以实现标签指示，可以实现标签切换页面和滑动切换页面，比较实用的一个组合。
/BlogClient/res/layout/main_head.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/light_blue"
android:orientation="horizontal" >

<!-- 自定义控件圆形ImageView -->
<com.xiaowu.blogclient.view.CircleImageView
android:id="@+id/head_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="8dp"
android:layout_marginRight="4dp"
android:src="@drawable/xiaowu" />

<!-- 分割线 -->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:src="@drawable/base_action_bar_back_divider" />

<!-- 头部文本 -->
<TextView
android:id="@+id/headTV"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="4dp"
android:layout_weight="1"
android:text="小巫CSDN博客"
android:textColor="@color/white"
android:textSize="21sp"
android:textStyle="bold" >
</TextView>

<ImageButton
android:id="@+id/personCenter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="@drawable/base_action_bar_action_more_selector"
android:visibility="invisible" />

</LinearLayout>

说明：头部的布局没啥可说的，就只是用到了一个圆形的imageView，也是自定义控件的使用。

侧边栏布局

/BlogClient/res/layout/person_center.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/wangyibg" >

<ImageView
android:id="@+id/background_img"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_marginTop="-100dp"
android:contentDescription="@null"
android:scaleType="fitXY"
android:src="@drawable/scrollview_header" />

<com.xiaowu.blogclient.view.PullScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:headerHeight="300dp"
app:headerVisibleHeight="100dp" >

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/transparent"
android:orientation="vertical" >

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginTop="5dp"
android:orientation="vertical" >

<com.xiaowu.blogclient.view.CircleImageView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/profile_image"
android:layout_width="96dp"
android:layout_height="96dp"
android:layout_gravity="center_horizontal"
android:src="@drawable/wwj_748"
app:border_color="#FFffffff"
app:border_width="1dp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="4dp"
android:text="IT_xiao小巫"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="4dp"
android:background="@color/transparent"
android:src="@drawable/ico_expert" />
</LinearLayout>

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="@drawable/biz_pc_account_line"
android:visibility="invisible" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:orientation="horizontal" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:text="勋章:"
android:textColor="@color/black"
android:textSize="16sp" />

<com.xiaowu.blogclient.view.CircleImageView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:background="@null"
android:src="@drawable/columnstar_s"
app:border_color="#FFffffff"
app:border_width="1dp" />

<com.xiaowu.blogclient.view.CircleImageView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:background="@null"
android:src="@drawable/holdon_s"
app:border_color="#FFffffff"
app:border_width="1dp" />
</LinearLayout>

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center_horizontal"
android:orientation="vertical" >

<TextView
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="访问：656748次"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:id="@+id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/tv1"
android:layout_below="@+id/tv1"
android:gravity="left"
android:text="积分：12296分"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:id="@+id/tv3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/tv2"
android:layout_below="@+id/tv2"
android:text="排名：第281名"
android:textColor="@color/black"
android:textSize="16sp" />
</RelativeLayout>

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp"
android:src="@drawable/biz_pc_account_line" />

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center_horizontal"
android:orientation="vertical" >

<TextView
android:id="@+id/tv4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="原创：526篇"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:id="@+id/tv5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_toRightOf="@+id/tv4"
android:gravity="left"
android:text="转载：81篇"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:id="@+id/tv6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/tv4"
android:layout_below="@+id/tv4"
android:text="译文：2篇"
android:textColor="@color/black"
android:textSize="16sp" />

<TextView
android:id="@+id/tv7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv5"
android:layout_marginLeft="5dp"
android:layout_toRightOf="@+id/tv6"
android:text="评论：956条"
android:textColor="@color/black"
android:textSize="16sp" />
</RelativeLayout>

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp"
android:src="@drawable/biz_pc_account_line" />

<RelativeLayout
android:id="@+id/checkUpdateView"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/list_selector" >

<TextView
android:id="@+id/tv8"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="20dp"
android:text="检查更新"
android:textColor="@color/black"
android:textSize="18sp" />

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:focusable="false"
android:src="@drawable/app_recommend_arrow" />
</RelativeLayout>

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="@drawable/biz_pc_account_line" />

<RelativeLayout
android:id="@+id/shareView"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/list_selector" >

<TextView
android:id="@+id/tv9"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="20dp"
android:text="分享好友"
android:textColor="@color/black"
android:textSize="18sp" />

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:focusable="false"
android:src="@drawable/app_recommend_arrow" />
</RelativeLayout>

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="@drawable/biz_pc_account_line" />

<RelativeLayout
android:id="@+id/aboutView"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/list_selector" >

<TextView
android:id="@+id/tv10"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="20dp"
android:text="关于"
android:textColor="@color/black"
android:textSize="18sp" />

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:focusable="false"
android:src="@drawable/app_recommend_arrow" />
</RelativeLayout>

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="@drawable/biz_pc_account_line" />

<Button
android:id="@+id/showSpot"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_selector"
android:text="展示插播广告" />

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="@drawable/biz_pc_account_line" />

<RelativeLayout
android:id="@+id/adLayout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal" >
</RelativeLayout>
</LinearLayout>
</com.xiaowu.blogclient.view.PullScrollView>

</RelativeLayout>

说明：侧边栏使用的是SlidingMenu——侧滑菜单，也是需要包含这个库。这里要提一下的是，使用到了有下拉回弹效果的ScrollView，一个自定义控件，在小巫的手机里效果还是可以的，在分辨率小的手机可能效果没那么好。

博文列表布局

/BlogClient/res/layout/article_list_layout.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/wangyibg"
tools:context=".MainFrag" >
<!-- 开源控件，具有顶部下拉和底部上拉刷新的效果 -->
<me.maxwin.view.XListView
android:id="@+id/blogListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:cacheColorHint="#00000000"
android:divider="@drawable/base_list_divider_drawable"
android:fadingEdge="none"
/>

<LinearLayout
android:id="@+id/noBlogLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="invisible">

<ImageView
android:id="@+id/no_blog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:src="@drawable/phiz"
android:visibility="visible" />
<TextView
android:id="@+id/tv_noblog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="博主还未发表文章，敬请期待！！！"/>
</LinearLayout>

</RelativeLayout>

博文列表项布局

/BlogClient/res/layout/article_list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="8dp" >

<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="Cocos2d-x 3.2示例UserDefaultTest（用户默认配置）"
android:textColor="@color/black"
android:textSize="18sp"
android:textStyle="bold" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="true"
android:orientation="horizontal" >

<ImageView
android:id="@+id/blogImg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:src="@drawable/csdn"
android:visibility="visible" />

<TextView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="false"
android:maxLines="4"
android:text="本篇博客介绍Cocos2d-x 3.2示例中的UserDefaulstTest,我们在开发中可能需要用到一些默认配置，一般会以xml形式保存。Cocos2d-x为我们提供了UserDefault类来实现这样的需求。"
android:textColor="@color/nomalGray"
android:textSize="14sp" />
</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical" >

<TextView
android:id="@+id/date"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical|right"
android:paddingTop="8dp"
android:singleLine="true"
android:text="2014-08-08 17:54|141人阅读"
android:textColor="@color/nomalGray"
android:textSize="12sp" />

<TextView
android:id="@+id/id"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="right"
android:paddingTop="16dp"
android:text="1"
android:textColor="@color/nomalGray"
android:visibility="gone" />
</LinearLayout>

</LinearLayout>

博文详细内容布局

/BlogClient/res/layout/article_detail.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/blogContentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/wangyibg"
android:clickable="true" >

<RelativeLayout
android:id="@+id/head"
android:layout_width="match_parent"
android:layout_height="wrap_content" >

<include layout="@layout/article_detail_head" />
</RelativeLayout>

<!-- 具有顶部下拉刷新和底部上拉刷新的ListView -->
<me.maxwin.view.XListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/head"
android:divider="@null" />

<ProgressBar
android:id="@+id/blogContentPro"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminateDrawable="@drawable/progressbar_large"
android:visibility="visible" />

<ImageView
android:id="@+id/reLoadImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:clickable="true"
android:src="@drawable/base_empty_view"
android:visibility="invisible" />

</RelativeLayout>

说明：博文详细内容的布局也是由XListView组成，所以我们可以知道整个布局就是一个ListView，然而ListView的项元素是由不同布局组成。

/BlogClient/res/layout/article_detail_title_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="16dp"
android:paddingBottom="8dp"
android:gravity="center_vertical"
android:textSize="21sp"
android:textStyle="bold"
android:text="" />
</LinearLayout>

说明：博文详细内容的标题项。

/BlogClient/res/layout/article_detail_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:lineSpacingExtra="8dp"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:gravity="center_vertical"
android:textSize="15sp"
android:text="" />
</LinearLayout>

说明：博文详细内容的文本项。
/BlogClient/res/layout/article_detail_bold_title_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:lineSpacingExtra="8dp"
android:paddingBottom="@dimen/activity_horizontal_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_horizontal_margin"
android:text=""
android:textColor="@color/coffee"
android:textSize="16sp"
android:textStyle="bold" />

</LinearLayout>

说明：博文详细内容粗标题项。

/BlogClient/res/layout/article_detail_img_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="@drawable/biz_topic_vote_submit_default"
android:layout_gravity="center_horizontal"/>
</LinearLayout>

说明：博文详细内容的图片项。
/BlogClient/res/layout/article_detail_code_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >

<WebView
android:id="@+id/code_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusableInTouchMode="true"
android:layout_margin="5dp"
/>
</LinearLayout>

说明：博文想详细内容的代码项。

上面关于博文详细内容有多个布局，是整个客户端最复杂的布局了，大家可以好好感受一下。

博文评论列表布局

/BlogClient/res/layout/activity_comment.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/newsContentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/wangyibg"
android:clickable="true" >

<RelativeLayout
android:id="@+id/head"
android:layout_width="match_parent"
android:layout_height="wrap_content" >

<include layout="@layout/comment_head" />
</RelativeLayout>

<me.maxwin.view.XListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/head"
android:divider="@drawable/base_list_divider_drawable" />

<ProgressBar
android:id="@+id/newsContentPro"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminateDrawable="@drawable/progressbar_large"
android:visibility="visible" />

<ImageView
android:id="@+id/reLoadImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:clickable="true"
android:src="@drawable/base_empty_view"
android:visibility="invisible" />
<!--
<ImageView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/base_list_divider_drawable"
/>
-->
<!--
<LinearLayout
android:id="@+id/commentEditL"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:background="@color/wangyibg"
>
<EditText
android:id="@+id/commentEdit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableLeft="@drawable/biz_news_newspage_comment_icon"
android:textSize="14sp"
android:drawablePadding="8dp"
android:layout_margin="6dp"
android:hint="添加评论"/>
<ImageButton
android:id="@+id/commentSend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/comment_btn"
android:layout_gravity="center_vertical"
android:layout_marginRight="4dp"/>
</LinearLayout>
-->

</RelativeLayout>

说明：评论列表跟博文详细内容的布局类似，也是由XListView组成，只是需要区分评论和回复评论的列表项，区分的逻辑是在代码中实现的，本篇博客之说明布局实现。

/BlogClient/res/layout/comment_head.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/biz_news_detaila_action_bar_bg"
android:orientation="horizontal" >

<ImageView
android:id="@+id/backBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="0dp"
android:layout_marginRight="0dp"
android:background="@drawable/back_btn"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/base_action_bar_back_divider"/>
<TextView
android:id="@+id/headTV"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:layout_marginLeft="4dp"
android:textColor="@color/white"
android:textStyle="bold"
android:textSize="21sp"
android:text="">
</TextView>
<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:visibility="gone" />
<TextView
android:id="@+id/comment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text=""
android:textColor="@color/white"
android:textSize="12sp"
android:layout_marginRight="4dp"
android:gravity="center"
android:background="@drawable/biz_news_detailpage_comment_normal"/>
<!-- <ImageButton
android:id="@+id/personCenter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/base_send_normal"
android:background="@drawable/person_btn"
/> -->

</LinearLayout>

说明：评论界面顶部布局。
/BlogClient/res/layout/comment_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="8dp" >

<com.xiaowu.blogclient.view.CircleImageView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/userface"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/csdn"
app:border_color="#FFffffff"
app:border_width="1dp" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:orientation="vertical" >

<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="5dp"
android:text="username"
android:textColor="@color/light_blue"
android:textSize="14sp" />

<!--
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/biz_pc_account_line" />
-->

<TextView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:text="mark"
android:ellipsize="none"
android:singleLine="false"
android:textSize="16sp" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:orientation="horizontal" >

<TextView
android:id="@+id/replyCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text=""
android:textColor="@color/nomalGray"
android:textSize="12sp" />

<TextView
android:id="@+id/date"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="right"
android:text="2013-10-11"
android:textColor="@color/nomalGray"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>

</LinearLayout>

说明：评论列表的主题项布局。

/BlogClient/res/layout/comment_child_item.xml

<?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:background="@color/wangyibg2" >

<ImageView
android:layout_width="match_parent"
android:layout_height="4dp"
android:background="@drawable/shadow_l" />

<TextView
android:id="@+id/replyIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="16dp"
android:drawableLeft="@drawable/material_go_normal"
android:gravity="center_vertical"
android:text="回复:"
android:textColor="@color/nomalGray" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="3dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="3dp"
android:layout_toRightOf="@+id/replyIcon"
android:orientation="vertical" >

<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:text="username"
android:textColor="@color/light_blue"
android:textSize="12sp" />
<!--
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/biz_pc_account_line"
/>
-->

<TextView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:text="mark"
android:ellipsize="none"
android:singleLine="false"
android:textColor="@color/black2"
android:textSize="14sp" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:orientation="horizontal" >

<TextView
android:id="@+id/replyCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text=""
android:textColor="@color/coffee"
android:textSize="12sp" />

<TextView
android:id="@+id/date"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="right"
android:text="2013-10-11"
android:textColor="@color/nomalGray"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>

</RelativeLayout>

说明：评论列表的回复项布局，跟主题项效果不一样，它是呈灰色背景的，来表示回复项。

UI篇总结

关于整个客户端的UI设计实现已经基本上介绍完，详细看来也没有多么复杂，小巫这里用到了很多自定义组件，让整个客户端好看很多。各位初学者可以根据自己的需求定制自己的UI，再整合一些优秀的开源组件，我相信大家一定能设计出简洁美观的界面。

Android应用开发-小巫CSDN博客客户端Jsoup篇

距上一篇博客已经过去了两个星期，小巫也觉得非常抱歉，因为在忙着做另外一个项目，几乎抽不出空来，这不小巫会把剩下的博文全部在国庆补上。本篇博客将会给大家介绍如何使用Jsoup这个库来解析我们的网页，并且如何对我们想解析的网页进行分析。

Jsoup这个库的下载地址：http://jsoup.org/download

笔者这里使用的jsoup-1.7.2

下载完之后复制到项目中的libs目录下即可：

Jsoup的资料比较少，可供参考的可到其官网进行学习这个库的使用：http://www.open-open.com/jsoup/

API查阅地址：http://jsoup.org/apidocs/

对这个库的使用笔者也不是非常熟悉，就只是简单参考了文档而完成了解析工作，所以下面的笔者的解析代码也只是提供参考，具体的解析方法，请认真查看api文档。

如何使用Jsoup这个库不是重点，重点是如何对我们想解析的网页进行分析，如何做到像以下的效果：

这里可以看到首页是获取笔者博客的博文列表，每一个条目都有对应的标题、博文摘要、发布时间、阅读数、评论数。这些内容就是通过解析首页html页面所得到的内容。

好，就是这样的一个html页面，大伙是不是很想把它搬到手机上呢，只要你学会了如何分析html页面，你可以把任何你想要的内容搬下来，只要你抓取的网页没有做防扒取的措施。

笔者使用的浏览器是Google Chrome，作为专业的IT人士，不用Chrome确实说不过去，按下F12，你就可以看到以下景象，感觉好兴奋啊，可以去寻找你想要的宝藏了。是啊，逗比可以往下看了：

如果想解析一个网页，还是得你自己以上面这种方式去看，找到你想要的内容。小巫是这样做的，可以通过右键查看元素，直接查看对应的html源代码，这样你就知道内容对应的标签是什么了。因为小巫想获得首页的所有博文列表，所以我就找到博文的最外层的div标签开始分析，我找到了id为article_list,然后接着找到了每条博文项的内容，确定下它们的具体的标签，使用了什么class，博客可以通过class来获取你想要的元素，然后取得元素的内容。

直接上代码吧：

/**
* 使用Jsoup解析html文档
*
* @param blogType
* @param str
* @return
*/
public static List<BlogItem> getBlogItemList(int blogType, String str) {
// Log.e("URL---->", str);
List<BlogItem> list = new ArrayList<BlogItem>();
// 获取文档对象
Document doc = Jsoup.parse(str);
// Log.e("doc--->", doc.toString());
// 获取class="article_item"的所有元素
Elements blogList = doc.getElementsByClass("article_item");
// Log.e("elements--->", blogList.toString());

for (Element blogItem : blogList) {
BlogItem item = new BlogItem();
String title = blogItem.select("h1").text(); // 得到标题
// System.out.println("title----->" + title);
String description = blogItem.select("div.article_description")
.text();
// System.out.println("descrition--->" + description);
String msg = blogItem.select("div.article_manage").text();
// System.out.println("msg--->" + msg);
String date = blogItem.getElementsByClass("article_manage").get(0)
.text();
// System.out.println("date--->" + date);
String link = BLOG_URL
+ blogItem.select("h1").select("a").attr("href");
// System.out.println("link--->" + link);
item.setTitle(title);
item.setMsg(msg);
item.setContent(description);
item.setDate(date);
item.setLink(link);
item.setType(blogType);

// 没有图片
item.setImgLink(null);
list.add(item);

}
return list;
}

小巫通过class="article_item"获取到所有的元素，也就是Element，然后遍历所有的元素，把每个元素我们需要的值取出来。我们可以定义一个实体类，比如文章项BlogItem,通过创建不同的BlogItem对象，然后最后添加到list当中，我们就可以把所有博文列表保存下拉，下次取的时候，直接通过list来取即可。

我们可以看到使用Jsoup这个库，只需要那么少的代码就可以轻松获取到我们想要的内容，编码、效率啥都有，还等什么，去用吧。

然后获取博文详细内容也是类似的，给定一个url，我们就可以同样的方式去解析html代码：

/**
* 扒取传入url地址的博客详细内容
*
* @param url
* @param str
* @return
*/
public static List<Blog> getContent(String url, String str) {
List<Blog> list = new ArrayList<Blog>();

// 获取文档内容
Document doc = Jsoup.parse(str);

// 获取class="details"的元素
Element detail = doc.getElementsByClass("details").get(0);
detail.select("script").remove(); // 删除每个匹配元素的DOM。

// 获取标题
Element title = detail.getElementsByClass("article_title").get(0);
Blog blogTitle = new Blog();
blogTitle.setState(Constants.DEF_BLOG_ITEM_TYPE.TITLE); // 设置状态
blogTitle.setContent(ToDBC(title.text())); // 设置标题内容

// 获取文章内容
Element content = detail.select("div.article_content").get(0);

// 获取所有标签为<a的元素
Elements as = detail.getElementsByTag("a");
for (int b = 0; b < as.size(); b++) {
Element blockquote = as.get(b);
// 改变这个元素的标记。例如,<span>转换为<div> 如el.tagName("div");。
blockquote.tagName("bold"); // 转为粗体
}

Elements ss = detail.getElementsByTag("strong");
for (int b = 0; b < ss.size(); b++) {
Element blockquote = ss.get(b);
blockquote.tagName("bold");
}

// 获取所有标签为<p的元素
Elements ps = detail.getElementsByTag("p");
for (int b = 0; b < ps.size(); b++) {
Element blockquote = ps.get(b);
blockquote.tagName("body");
}

// 获取所有引用元素
Elements blockquotes = detail.getElementsByTag("blockquote");
for (int b = 0; b < blockquotes.size(); b++) {
Element blockquote = blockquotes.get(b);
blockquote.tagName("body");
}

// 获取所有标签为<ul的元素
Elements uls = detail.getElementsByTag("ul");
for (int b = 0; b < uls.size(); b++) {
Element blockquote = uls.get(b);
blockquote.tagName("body");
}

// 找出粗体
Elements bs = detail.getElementsByTag("b");
for (int b = 0; b < bs.size(); b++) {
Element bold = bs.get(b);
bold.tagName("bold");
}

// 遍历博客内容中的所有元素
for (int j = 0; j < content.children().size(); j++) {
Element c = content.child(j); // 获取每个元素

// 抽取出图片
if (c.select("img").size() > 0) {
Elements imgs = c.getElementsByTag("img");
System.out.println("img");
for (Element img : imgs) {
if (!img.attr("src").equals("")) {
Blog blogImgs = new Blog();
// 大图链接
if (!img.parent().attr("href").equals("")) {
blogImgs.setImgLink(img.parent().attr("href"));
System.out.println("href="
+ img.parent().attr("href"));
if (img.parent().parent().tagName().equals("p")) {
// img.parent().parent().remove();
}
img.parent().remove();
}
blogImgs.setContent(img.attr("src"));
blogImgs.setImgLink(img.attr("src"));
System.out.println(blogImgs.getContent());
blogImgs.setState(Constants.DEF_BLOG_ITEM_TYPE.IMG);
list.add(blogImgs);
}
}
}
c.select("img").remove();

// 获取博客内容
Blog blogContent = new Blog();
blogContent.setState(Constants.DEF_BLOG_ITEM_TYPE.CONTENT);

if (c.text().equals("")) {
continue;
} else if (c.children().size() == 1) {
if (c.child(0).tagName().equals("bold")
|| c.child(0).tagName().equals("span")) {
if (c.ownText().equals("")) {
// 小标题，咖啡色
blogContent
.setState(Constants.DEF_BLOG_ITEM_TYPE.BOLD_TITLE);
}
}
}

// 代码
if (c.select("pre").attr("name").equals("code")) {
blogContent.setState(Constants.DEF_BLOG_ITEM_TYPE.CODE);
blogContent.setContent(ToDBC(c.outerHtml()));
} else {
blogContent.setContent(ToDBC(c.outerHtml()));
}
list.add(blogContent);
}

return list;
}

获取评论列表：

/**
* 获取博文评论列表
*
* @param str
*            json字符串
* @return
*/
public static List<Comment> getBlogCommentList(String str, int pageIndex,
int pageSize) {
List<Comment> list = new ArrayList<Comment>();
try {
// 创建一个json对象
JSONObject jsonObject = new JSONObject(str);
JSONArray jsonArray = jsonObject.getJSONArray("list"); // 获取json数组
int index = 0;
int len = jsonArray.length();
BlogCommentActivity.commentCount = String.valueOf(len); // 评论条数
// 如果评论数大于20
if (len > 20) {
index = (pageIndex * pageSize) - 20;
}

if (len < pageSize && pageIndex > 1) {
return list;
}

if ((pageIndex * pageSize) < len) {
len = pageIndex * pageSize;
}

for (int i = index; i < len; i++) {
JSONObject item = jsonArray.getJSONObject(i);
String commentId = item.getString("CommentId");
String content = item.getString("Content");
String username = item.getString("UserName");
String parentId = item.getString("ParentId");
String postTime = item.getString("PostTime");
String userface = item.getString("Userface");

Comment comment = new Comment();
comment.setCommentId(commentId);
comment.setContent(content);
comment.setUsername(username);
comment.setParentId(parentId);
comment.setPostTime(postTime);
comment.setUserface(userface);

if (parentId.equals("0")) {
// 如果parentId为0的话，表示它是评论的topic
comment.setType(Constants.DEF_COMMENT_TYPE.PARENT);
} else {
comment.setType(Constants.DEF_COMMENT_TYPE.CHILD);
}
list.add(comment);
}

} catch (JSONException e) {
e.printStackTrace();
}
return list;
}

具体使用细节可以参考笔者提供的源码：http://download.csdn.net/detail/wwj_748/7912513

小巫已经把解析html的思路告诉大家啦，剩下如何来学习jsoup这个库解析html就靠你们自己去做了。下一篇博客预告，集成友盟社会化组件，详细给大家介绍如何集成友盟提供的社会化组件SDK。

Android应用开发-小巫CSDN博客客户端之集成友盟社会化分享组件

上一篇博客给大家介绍了如何分析网页并且使用jsoup这个库对html代码进行解析，本篇博客继续给大家介绍如何集成友盟社会化组件，如何使用SDK提供的API轻松实现多平台的社会化分享，官网的文档和Demo看起来很头疼的有木有，小巫在集成这个社会化的组件也有点烦躁，所以也需要各位耐心看下面的博文把友盟社会化组件集成到你的应用中去。为什么要选择友盟呢，这里也是答应了小喵的，要帮忙集成他们的服务，所以也顺带帮他们写一篇这样的博文，千万不要感动啊。

先来介绍一下：友盟社会化组件的产品特点：

主流OS

iOS、Android、Windows Phone、PhoneGap

游戏引擎

Cocos2d-x、Unity

主流社交平台

微信、QQ、新浪微博、腾讯微博、来往、易信、人人网、豆瓣，Facebook、Twitter、 Instagram、Google+、短信、邮件

- 集成4个社交模块（登录-分享-评论-Like），可分别调用，灵活定制；

- 授权登录的同时，可勾选关注官方微博；

- 分享：支持图片、文字、gif动图、音频、视频；

- 登录：支持新浪微博SSO，后续支持更多平台SSO；

- 支持@好友、地理位置签到、表情；

- 新鲜事文本支持@APP官方微博；

- 支持横屏、竖屏、HD版；

友盟社会化行为分析

针对自建社交模块的移动应用提供的社会化统计分析SDK，可实时了解SNS、用户、信息流、转化率、传播效应等数据。

提供四个维度： 概况、用户、行为、主题（内容）的趋势信息；

时间区间：24小时时段、日、周、月、年，未来可支持自定义；

提供总体的[用户分布]（包括性别、年龄、学历、地域、职业、特征、爱好等）及详细的[用户列表]信息，精准至个体，并支持定向筛选；

贡献值排名：根据操作行为的权重排序，掌握最核心的用户；

监测带有话题或关键字的活动在社交平台的传播效应，覆盖Web与Mobile；

先来给大伙看一下集成效果：

下面是详细的集成过程：

首先来到官网：http://www.umeng.com/

选择社会化分享，进入查看这个产品的介绍，下载对应平台的SDK，笔者这里自然是选择Android平台：

笔者集成友盟社会化组件的时候还未更新到4.1，所以使用的4.0版的，点击下载就可以选择你想要集成分享的平台，还有你想要的文档或者Demo:

这些操作都可以自行到官网进行查看，笔者就不再浪费口舌。

下载完后，解压缩可以看到以下目录和文件：

platforms目录下对应了不同的平台的所需要的资源和jar包：

然后选择你想要集成的平台，复制的res和libs到你的目标项目中去：

这个操作做完之后，在AndroidManifest.xml中增加必要的配置：

<!-- ************* 分享相关的注册 START **************-->
<!-- 分享内容编辑页 -->
<activity
android:name="com.umeng.socialize.view.ShareActivity"
android:configChanges="orientation|keyboard"
android:launchMode="singleTask"
android:noHistory="true"
android:theme="@style/Theme.UMDialog"
android:windowSoftInputMode="stateVisible|adjustResize" >
</activity>

<!-- 腾讯SSO授权的Activity注册 -->
<activity
android:name="com.tencent.tauth.AuthActivity"
android:launchMode="singleTask"
android:noHistory="true" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="tencent100424468" />
</intent-filter>
</activity>
<activity
android:name="com.tencent.connect.common.AssistActivity"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />

<!-- 微信回调activity -->
<activity
android:name="com.xiaowu.blogclient.umeng.WXEntryActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="true"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />

<!-- 易信回调activity -->
<activity
android:name=".yxapi.YXEntryActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="true"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />

<!-- 人人SSO授权所需的OAuthActivity -->
<activity
android:name="com.renn.rennsdk.oauth.OAuthActivity"
android:configChanges="orientation|navigation|keyboardHidden" />

<!-- facebook相关 -->
<activity android:name=".activity.FacebookActivity" >
</activity>
<activity
android:name="com.facebook.LoginActivity"
android:label="@string/app_name"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />

<!-- ************* 分享相关的注册 END **************-->

然后在代码中进行如下的初始化操作，小巫已经把各个平台的使用写在以下方法：

/**
* @功能描述 : 初始化与SDK相关的成员变量
*/
private void initConfig() {
mContext = getActivity();
mController = UMServiceFactory.getUMSocialService(DESCRIPTOR);

// 要分享的文字内容
mShareContent = "小巫CSDN博客客户端，CSDN移动开发专家——IT_xiao小巫的专属客户端，你值得拥有。";
mController.setShareContent(mShareContent);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.xiaowu);

mUMImgBitmap = new UMImage(mContext, bitmap);
mController.setShareImage(mUMImgBitmap);
mController.setAppWebSite(""); // 设置应用地址

// 添加新浪和qq空间的SSO授权支持
mController.getConfig().setSsoHandler(new SinaSsoHandler());
// 添加腾讯微博SSO支持
mController.getConfig().setSsoHandler(new TencentWBSsoHandler());

// wx967daebe835fbeac是你在微信开发平台注册应用的AppID, 这里需要替换成你注册的AppID
String appID = "wx880cb2b22509cf25";
// 添加微信平台
UMWXHandler wxHandler = new UMWXHandler(getActivity(), appID);
wxHandler.addToSocialSDK();
// 支持微信朋友圈
UMWXHandler wxCircleHandler = new UMWXHandler(getActivity(), appID);
wxCircleHandler.setToCircle(true);
wxCircleHandler.addToSocialSDK();

// 设置微信好友分享内容
WeiXinShareContent weixinContent = new WeiXinShareContent();
// 设置分享文字
weixinContent.setShareContent(mShareContent);
// 设置title
weixinContent.setTitle("小巫CSDN博客客户端");
// 设置分享内容跳转URL
weixinContent.setTargetUrl("你的http://blog.csdn.net/wwj_748链接");
// 设置分享图片
weixinContent.setShareImage(mUMImgBitmap);
mController.setShareMedia(weixinContent);

// 设置微信朋友圈分享内容
CircleShareContent circleMedia = new CircleShareContent();
circleMedia.setShareContent(mShareContent);
// 设置朋友圈title
circleMedia.setTitle("小巫CSDN博客客户端");
circleMedia.setShareImage(mUMImgBitmap);
circleMedia.setTargetUrl("你的http://blog.csdn.net/wwj_748链接");
mController.setShareMedia(circleMedia);

// 参数1为当前Activity，参数2为开发者在QQ互联申请的APP ID，参数3为开发者在QQ互联申请的APP kEY.
UMQQSsoHandler qqSsoHandler = new UMQQSsoHandler(getActivity(),
"1102369913", "62ru775qbkentOUp");
qqSsoHandler.addToSocialSDK();

// 参数1为当前Activity，参数2为开发者在QQ互联申请的APP ID，参数3为开发者在QQ互联申请的APP kEY.
QZoneSsoHandler qZoneSsoHandler = new QZoneSsoHandler(getActivity(),
"1102369913", "62ru775qbkentOUp");
qZoneSsoHandler.addToSocialSDK();

// 添加人人网SSO授权功能
// APPID:201874
// API Key:28401c0964f04a72a14c812d6132fcef
// Secret:3bf66e42db1e4fa9829b955cc300b737
RenrenSsoHandler renrenSsoHandler = new RenrenSsoHandler(getActivity(),
"271529", "682c45dbdeba4b608922fef124223efb",
"2c7c3b63f58b4bfcad3665b49e65d47f");
mController.getConfig().setSsoHandler(renrenSsoHandler);

// 添加短信
SmsHandler smsHandler = new SmsHandler();
smsHandler.addToSocialSDK();

// 添加email
EmailHandler emailHandler = new EmailHandler();
emailHandler.addToSocialSDK();

QQShareContent qqShareContent = new QQShareContent();
qqShareContent.setShareContent(mShareContent);
qqShareContent.setTitle("小巫CSDN博客");
qqShareContent.setShareImage(mUMImgBitmap);
qqShareContent.setTargetUrl("http://blog.csdn.net/wwj_748");
mController.setShareMedia(qqShareContent);

QZoneShareContent qzone = new QZoneShareContent();
// 设置分享文字
qzone.setShareContent(mShareContent);
// 设置点击消息的跳转URL
qzone.setTargetUrl("http://blog.csdn.net/wwj_748");
// 设置分享内容的标题
qzone.setTitle("小巫CSDN博客");
// 设置分享图片
qzone.setShareImage(mUMImgBitmap);
mController.setShareMedia(qzone);

}

通过openShare()方法打开分享盘：

/**
* 打开分享盘
*/
private void openShareBoard() {
mController.openShare(getActivity(), false);

}

如果使用了SSO授权，有以下代码：

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
/**使用SSO授权必须添加如下代码 */
UMSsoHandler ssoHandler = mController.getConfig().getSsoHandler(
requestCode);
if (ssoHandler != null) {
ssoHandler.authorizeCallBack(requestCode, resultCode, data);
}
}

这个基本上就是友盟社会化组件的使用方法，具体每个平台涉及到appkey或者appScrent可以到具体平台创建应用获取：

新浪微博     http://open.weibo.com
腾讯微博     http://dev.t.qq.com
QQ空间        http://connect.qq.com/intro/login/
微信好友      http://open.weixin.qq.com
Facebook      https://developers.facebook.com
Twitter       https://dev.twitter.com
人人网       http://dev.renren.com
开心网        http://open.kaixin001.com
搜狐微博     http://open.t.sohu.com
网易微博     http://open.t.163.com
豆瓣          http://developers.douban.com
有道云笔记    http://note.youdao.com/open/developguide.html#app
印象笔记      https://dev.evernote.com/
Linkedin      https://www.linkedin.com/secure/developer?newapp=
FourSquare    https://developer.foursquare.com/
搜狐随身看   https://open.sohu.com/
Flickr        http://www.flickr.com/services/
Pinterest     http://developers.pinterest.com/
Tumblr        http://www.tumblr.com/developers
Dropbox       https://www.dropbox.com/developers
Instagram     http://instagram.com/developer#
VKontakte     http://vk.com/dev

如果上面有介绍的有遗漏的地方或者不明白的地方，大家可以给小巫留言，小巫会及时补上。

另外小巫CSDN博客客户端也集成了友盟更新组件，并且已经写过一篇评测报告，大家可以到以下地址查看：

http://www.devstore.cn/evaluation/testInfo/113-177.html

下篇博客预告：有米广告模块的集成

Android应用开发-小巫CSDN博客客户端之嵌入有米广告

上一篇博客给大家介绍如何集成友盟社会化组件，本篇继续带来干货，教大家如何嵌入广告到应用中去。小巫自称专业对接30年，熟悉各大渠道SDK的接入和使用，除非渠道提供的SDK很坑，不然只需要不到半个小时的时间就可以把SDK接入到应用当中。关于广告，是开发者比较关注的话题，为什么要嵌入广告呢，自然是为了为自己的辛苦劳作得到些许额外的收益，因为在国内除非做IOS付费软件，不然在Android平台下做收费软件是很难获得收益的，用户只想用看起来好用并且免费的东西，嵌入广告是唯一能行得通的办法，只要不会太离谱就行。

广告服务商很多，比如有米、点金、多盟、广点通等等，开发者可以选择口碑比较好，能为开发者带来较好的收益的广告渠道。

笔者这里选择了有米作为实验点，提供一个参考给大家，废话不多说，先来看看小巫CSDN博客客户端现在的收益：

有木有看到收入啊，囧，很抱歉这点收入，小巫这个客户端的广告隐藏得很深，用户也不是很多，所以就暂时就只有这点啦。

同样的，下面是有米广告的集成过程：

到有米广告官网注册帐号，在管理中心创建应用获取到发布ID和应用密钥：

创建完应用之后，下载SDK并且解压缩，得到如下文件夹和文件：

demo对应相应的示例项目，开发者可以通过这些示例项目，可以快速实现集成SDK。

doc对应开发文档

libs对应集成时需要的库

说实在集成SDK并没有什么技术含量，只要你按照渠道提供的开发文档，自己试一遍，基本上就没有什么疑惑了。

有米给我们提供了以下几种广告类型：

- 广告条，也称横幅

- 插屏广告

- 积分墙广告

然而横幅广告有两种形式，一种是以XML形式布局，另一种是以代码形式布局。

配置有米权限：

<!-- 有米广告配置权限 -->
<!-- 配置权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.GET_TASKS" />
<!-- 以下为可选权限 -->
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />

有米必要的配置：

<!-- 有米广告必须添加的组件 -->
<!-- 添加必须的组件 -->
<activity
android:name="net.youmi.android.AdBrowser"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:theme="@android:style/Theme.Light.NoTitleBar" >
</activity>

<service
android:name="net.youmi.android.AdService"
android:exported="false" >
</service>

<receiver android:name="net.youmi.android.AdReceiver" >
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />

<data android:scheme="package" />
</intent-filter>
</receiver>

<!-- 有米渠道号(可选配置) 渠道号不能带空格，类型为整数 -->
<meta-data
android:name="YOUMI_CHANNEL"
android:value="0" >
</meta-data>

有米广告初始化：

// 初始化应用的发布 ID 和密钥，以及设置测试模式
AdManager.getInstance(this).init("您的应用发布ID", "您的应用密钥", false);

小巫的博客客户端至集成了广告条和插屏广告，效果如下：

代码中的实现：

/**
* 初始化广告
*
* @param view
*/
public void initAd(View view) {
// 初始化接口，应用启动的时候调用
// 参数：appId, appSecret, 调试模式
AdManager.getInstance(getActivity()).init("8df70b90ebf86823",
"b7659d08439c052b", false);
// // 广告条接口调用（适用于应用）
// // 将广告条adView添加到需要展示的layout控件中
// LinearLayout adLayout = (LinearLayout)
// view.findViewById(R.id.adLayout);
// AdView adView = new AdView(getActivity(), AdSize.FIT_SCREEN);
// adLayout.addView(adView);

// 普通布局，适用于应用
// 获取要嵌入迷你广告条的布局
RelativeLayout adLayout = (RelativeLayout) view
.findViewById(R.id.adLayout);
// demo 1 迷你Banner : 宽满屏，高32dp
DiyBanner banner = new DiyBanner(getActivity(),
DiyAdSize.SIZE_MATCH_SCREENx32);// 传入高度为32dp的AdSize来定义迷你Banner
// demo 2 迷你Banner : 宽320dp，高32dp
// DiyBanner banner = new DiyBanner(this,
// DiyAdSize.SIZE_320x32);//传入高度为32dp的AdSize来定义迷你Banner
// 将积分Banner加入到布局中
adLayout.addView(banner);

// 监听广告条接口
// adView.setAdListener(new AdViewListener() {
//
// @Override
// public void onSwitchedAd(AdView arg0) {
// Log.i("YoumiAdDemo", "广告条切换");
// }
//
// @Override
// public void onReceivedAd(AdView arg0) {
// Log.i("YoumiAdDemo", "请求广告成功");
// }
//
// @Override
// public void onFailedToReceivedAd(AdView arg0) {
// Log.i("YoumiAdDemo", "请求广告失败");
// }
// });

// 插播接口调用
// 开发者可以到开发者后台设置展示频率，需要到开发者后台设置页面（详细信息->业务信息->无积分广告业务->高级设置）
// 自4.03版本增加云控制是否开启防误点功能，需要到开发者后台设置页面（详细信息->业务信息->无积分广告业务->高级设置）

// 加载插播资源
SpotManager.getInstance(getActivity()).loadSpotAds();
// 设置展示超时时间，加载超时则不展示广告，默认0，代表不设置超时时间
SpotManager.getInstance(getActivity()).setSpotTimeout(5000);// 设置5秒
SpotManager.getInstance(getActivity()).setShowInterval(20);// 设置20秒的显示时间间隔
// 如需要使用自动关闭插屏功能，请取消注释下面方法
SpotManager.getInstance(getActivity()).setAutoCloseSpot(true);// 设置自动关闭插屏开关
SpotManager.getInstance(getActivity()).setCloseTime(6000); // 设置关闭插屏时间

}

展示插屏广告的代码：

// 展示插播广告，可以不调用loadSpot独立使用
SpotManager.getInstance(getActivity()).showSpotAds(getActivity(),
new SpotDialogListener() {
@Override
public void onShowSuccess() {
Log.i("YoumiAdDemo", "展示成功");
}

@Override
public void onShowFailed() {
Log.i("YoumiAdDemo", "展示失败");
}

});

声明周期中的插屏广告的处理：

@Override
public void onStop() {
// 如果不调用此方法，则按home键的时候会出现图标无法显示的情况。
SpotManager.getInstance(getActivity()).disMiss(false);
super.onStop();
}

@Override
public void onDestroy() {
// 取消注册监听
SpotManager.getInstance(getActivity()).unregisterSceenReceiver();
super.onDestroy();
}

以上就是关于嵌入有米广告的过程，如何长正式产生收益呢，需要应用审核通过呈运行状态，就开始计费了。只要你的应用做得足够吸引人，我想用户也不介意多点几下作为汇报，看看Flappy Bird就是一个很好的例子了。

下一篇博文预告是，如何用ListView呈现文章详细内容，谢谢朋友们的耐心阅读，你的支持是小巫的无限动力。

Android应用开发-小巫CSDN博客客户端之显示博文详细内容

上篇博文给大家介绍的是如何嵌入有米广告并且获取收益，本篇博客打算讲讲关于如何在一个ListView里显示博文的详细信息，这个可能是童鞋们比较困惑的，因为一篇博客可能有标题、摘要、图片、代码等等元素组成，我们要怎么在一个界面中显示这些内容并且按照自己的指定的方式显示呢，别急，下面会告诉大家。

重新整理一下一篇博文可能有以下元素：

- 标题

- 摘要

- 文本内容

- 图片

- 粗标题

- 代码块

在UI篇小巫已经介绍了，博文详细内容的主要控件就是一个ListView,每个元素就是ListView中的一项item，每一项都有自己的布局用于显示特定的元素。效果图如下:

关于UI就不说了，主要来看一下内容适配器：

/BlogClient/src/com/xiaowu/blogclient/adapter/BlogDetailAdapter.java

package com.xiaowu.blogclient.adapter;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Bitmap;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import com.xiaowu.blogclient.R;
import com.xiaowu.blogclient.model.Blog;
import com.xiaowu.blogclient.util.Constants;
import com.xiaowu.blogclient.util.FileUtil;
import com.xiaowu.blogclient.util.MyTagHandler;

/**
* 博客内容适配器
*
* @author wwj_748
* @date 2014/8/10
*/
public class BlogDetailAdapter extends BaseAdapter {
private ViewHolder holder;
private LayoutInflater layoutInflater;
private Context context;
private List<Blog> list;

private SpannableStringBuilder htmlSpannable;
private ImageLoader imageLoader = ImageLoader.getInstance();
private DisplayImageOptions options;

public BlogDetailAdapter(Context context) {
super();
this.context = context;
layoutInflater = LayoutInflater.from(context);
list = new ArrayList<Blog>();

// 初始化imageLoader
imageLoader.init(ImageLoaderConfiguration.createDefault(context));

// imageloader配置
options = new DisplayImageOptions.Builder()
.showStubImage(R.drawable.images)
.showImageForEmptyUri(R.drawable.images)
.showImageOnFail(R.drawable.images).cacheInMemory()
.cacheOnDisc().imageScaleType(ImageScaleType.EXACTLY)
.bitmapConfig(Bitmap.Config.RGB_565)
.displayer(new FadeInBitmapDisplayer(300)).build();

}

public void setList(List<Blog> list) {
this.list = list;
}

public void addList(List<Blog> list) {
this.list.addAll(list);
}

public void clearList() {
this.list.clear();
}

public List<Blog> getList() {
return list;
}

public void removeItem(int position) {
if (list.size() > 0) {
list.remove(position);
}
}

@Override
public int getCount() {
return list.size();
}

@Override
public Object getItem(int position) {
return list.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
Blog item = list.get(position);
if (null == convertView) {
holder = new ViewHolder();
switch (item.getState()) {
case Constants.DEF_BLOG_ITEM_TYPE.TITLE:// 显示标题
convertView = layoutInflater.inflate(
R.layout.article_detail_title_item, null);
holder.content = (TextView) convertView.findViewById(R.id.text);
break;
case Constants.DEF_BLOG_ITEM_TYPE.SUMMARY: // 摘要
convertView = layoutInflater.inflate(
R.layout.article_detail_summary_item, null);
holder.content = (TextView) convertView.findViewById(R.id.text);
break;
case Constants.DEF_BLOG_ITEM_TYPE.CONTENT: // 内容
convertView = layoutInflater.inflate(
R.layout.article_detail_item, null);
holder.content = (TextView) convertView.findViewById(R.id.text);
break;
case Constants.DEF_BLOG_ITEM_TYPE.IMG: // 图片
convertView = layoutInflater.inflate(
R.layout.article_detail_img_item, null);
holder.image = (ImageView) convertView
.findViewById(R.id.imageView);
break;
case Constants.DEF_BLOG_ITEM_TYPE.BOLD_TITLE: // 加粗标题
convertView = layoutInflater.inflate(
R.layout.article_detail_bold_title_item, null);
holder.content = (TextView) convertView.findViewById(R.id.text);
break;
case Constants.DEF_BLOG_ITEM_TYPE.CODE: // 代码
convertView = layoutInflater.inflate(
R.layout.article_detail_code_item, null);
holder.code = (WebView) convertView
.findViewById(R.id.code_view);
// holder.code.getSettings().setUseWideViewPort(true);
// holder.code.getSettings().setJavaScriptEnabled(true);
// holder.code.getSettings().setSupportZoom(true);
// holder.code.getSettings().setBuiltInZoomControls(false);
// holder.code.getSettings().setLoadWithOverviewMode(true);
break;
}

convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// System.out.println(item.getContent());

if (null != item) {
switch (item.getState()) {
case Constants.DEF_BLOG_ITEM_TYPE.IMG: // 图片，异步加载
imageLoader.displayImage(item.getContent(), holder.image,
options);
break;
case Constants.DEF_BLOG_ITEM_TYPE.CODE: // 代码，格式显示

// 读取代码文件和模板文件
String code = item.getContent();
// String code = FileUtil.getFileContent(context,
// "AboutActivity.java");
String template = FileUtil.getFileContent(context, "code.html");
// 生成结果
String html = template.replace("{{code}}", code);
holder.code.getSettings().setDefaultTextEncodingName("utf-8");
holder.code.getSettings().setSupportZoom(true);
holder.code.getSettings().setBuiltInZoomControls(true);

// holder.code.loadUrl("file:///android_asset/code.html");

holder.code.loadDataWithBaseURL("file:///android_asset/", html,
"text/html", "utf-8", null);

break;
default:
holder.content.setText(Html.fromHtml(item.getContent(), null,
new MyTagHandler()));
break;
}
}
return convertView;
}

@Override
public int getViewTypeCount() {
return 6;
}

@Override
public int getItemViewType(int position) {
switch (list.get(position).getState()) {
case Constants.DEF_BLOG_ITEM_TYPE.TITLE:
return 0;
case Constants.DEF_BLOG_ITEM_TYPE.SUMMARY:
return 1;
case Constants.DEF_BLOG_ITEM_TYPE.CONTENT:
return 2;
case Constants.DEF_BLOG_ITEM_TYPE.IMG:
return 3;
case Constants.DEF_BLOG_ITEM_TYPE.BOLD_TITLE:
return 4;
case Constants.DEF_BLOG_ITEM_TYPE.CODE:
return 5;
}
return 1;
}

@Override
public boolean isEnabled(int position) {
switch (list.get(position).getState()) {
case Constants.DEF_BLOG_ITEM_TYPE.IMG:
return true;
default:
return false;
}
}

private class ViewHolder {
TextView id;
TextView date;
TextView title;
TextView content;
ImageView image;
WebView code;
}
}

这里有一个ListView的优化策略，就是图片进行异步加载，小巫这里用到了优秀的开源项目universalimageloader，我们只需要关联依赖项目，就可以在项目中使用它对网络图片进行异步加载，具体使用自己查看上面的代码实现。

/BlogClient/src/com/xiaowu/blogclient/BlogDetailActivity.java

package com.xiaowu.blogclient;

import me.maxwin.view.IXListViewLoadMore;
import me.maxwin.view.XListView;
import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.xiaowu.blogclient.adapter.BlogDetailAdapter;
import com.xiaowu.blogclient.util.Constants;
import com.xiaowu.blogclient.util.JsoupUtil;
import com.xiaowu.blogclient.util.HttpUtil;

/**
* 博客详细内容界面
*
* @author wwj_748
* @date 2014/8/10
*/
public class BlogDetailActivity extends Activity implements IXListViewLoadMore {
private XListView listView; // 列表控件
private BlogDetailAdapter blogDetailAdapter; // 内容适配器

private ProgressBar progressBar; // 进度条
private ImageView reLoadImageView; // 重新加载的图片

private ImageView backBtn; // 回退按钮
private ImageView commentBtn; // 评论按钮

public static String url; // 博客地址
private String filename; // 文件名字

@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);// 无标题
super.onCreate(savedInstanceState);
setContentView(R.layout.article_detail);

init();
initComponent();

// 执行异步加载
new MainTask().execute(url, Constants.DEF_TASK_TYPE.FIRST);
}

// 初始化
private void init() {
blogDetailAdapter = new BlogDetailAdapter(this);
url = getIntent().getExtras().getString("blogLink");
filename = url.substring(url.lastIndexOf("/") + 1);
System.out.println("filename--->" + filename);
}

// 初始化组件
private void initComponent() {
progressBar = (ProgressBar) findViewById(R.id.blogContentPro);
reLoadImageView = (ImageView) findViewById(R.id.reLoadImage);
reLoadImageView.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View view) {
reLoadImageView.setVisibility(View.INVISIBLE);
progressBar.setVisibility(View.VISIBLE);

}
});

backBtn = (ImageView) findViewById(R.id.backBtn);
backBtn.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View arg0) {
finish();
}
});

commentBtn = (ImageView) findViewById(R.id.comment);
commentBtn.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View view) {
Intent i = new Intent();
i.setClass(BlogDetailActivity.this, BlogCommentActivity.class);
i.putExtra("filename", filename);
startActivity(i);
overridePendingTransition(R.anim.push_left_in, R.anim.push_no);
}
});

listView = (XListView) findViewById(R.id.listview);
listView.setAdapter(blogDetailAdapter);
listView.setPullLoadEnable(this);

listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// 获取点击列表项的状态
int state = blogDetailAdapter.getList().get(position - 1)
.getState();
switch (state) {
case Constants.DEF_BLOG_ITEM_TYPE.IMG: // 点击的是图片
String url = blogDetailAdapter.getList().get(position - 1)
.getImgLink();
Intent i = new Intent();
i.setClass(BlogDetailActivity.this, ImageActivity.class);
i.putExtra("url", url);
startActivity(i);
break;
default:
break;
}
}
});
}

@Override
public void finish() {
super.finish();
}

private class MainTask extends AsyncTask<String, Void, Integer> {

@Override
protected Integer doInBackground(String... params) {
// 通过http请求url地址，获取html文档
String temp = HttpUtil.httpGet(params[0]);
if (temp == null) {
if (params[1].equals(Constants.DEF_TASK_TYPE.FIRST)) {
return Constants.DEF_RESULT_CODE.FIRST;
} else {
return Constants.DEF_RESULT_CODE.ERROR;
}
}
// 添加解析出来的数据
blogDetailAdapter.addList(JsoupUtil.getContent(url, temp));
if (params[1].equals(Constants.DEF_TASK_TYPE.FIRST)) {
return Constants.DEF_RESULT_CODE.REFRESH;
}
return Constants.DEF_RESULT_CODE.LOAD;
}

@Override
protected void onPostExecute(Integer result) {
if (result == Constants.DEF_RESULT_CODE.FIRST) {
Toast.makeText(getApplicationContext(), "网络信号不佳",
Toast.LENGTH_LONG).show();
reLoadImageView.setVisibility(View.VISIBLE);
} else if (result == Constants.DEF_RESULT_CODE.ERROR) {
listView.stopLoadMore();
} else if (result == Constants.DEF_RESULT_CODE.REFRESH) {
blogDetailAdapter.notifyDataSetChanged();
} else {
blogDetailAdapter.notifyDataSetChanged();
listView.stopLoadMore();
}
progressBar.setVisibility(View.INVISIBLE);
super.onPostExecute(result);
}

}

// 加载更多
@Override
public void onLoadMore() {
if (!JsoupUtil.contentLastPage) {
new MainTask().execute(url, Constants.DEF_TASK_TYPE.NOR_FIRST);
} else {
// 在底部显示“--THE END0--"文本
listView.stopLoadMore(" -- THE END --");
}
}

}

如果研读了我提供源码的童鞋都会发现，我这里使用AsyncTask来进行网络请求操作，童鞋们也可以使用Thread+Handler的方式来实现异步请求，需要注意的是，耗时操作和网络操作都不能放在主线程，这是Android开发的规范。

这里还提一个技巧，我们更新ListView的数据的时候，并不需要重新new一个adapter，可以像我一样，在适配器类中提供addList的方法，添加数据到adapter中，然后在适当的位置调用notifyDataSetChanged()方法就可以更新数据，不会出现数据重复和效率低下的情况。

Android应用开发-小巫CSDN博客客户端之获取评论列表

上一篇博客介绍了博文详细内容的业务逻辑实现，本篇博客介绍小巫CSDN博客客户端的最后一项功能，获取评论列表，这个功能的实现跟前面获取文章列表和文章详细的内容不一样，CSDN博客获取评论是通过js来请求服务器加载评论列表的，返回数据为json数据，我们这里要做的事情就是找到这样的一个js文件，再找到请求url的拼接字符串，然后根据我们的需求，请求文章的评论列表获取到当前文章的评论json数据，然后进行解析工作，最后展示到我们的界面当中。

如果没有仔细分析html代码的童鞋，可能发现不了这一点，小巫在开发这个客户端的时候，一时也获取不到评论列表，后来通过与CSDN技术的交流之后，我再仔细查看才找到了关于博客的请求方式，这里使用jsoup无法模拟javascript的加载，所以只能通过自己查看js代码，找到请求的url，下面笔者会告诉大家怎么来做这件事。

小巫这里找一篇有评论的博文，比如以下这篇：

http://blog.csdn.net/wwj_748/article/details/39726051

我们可以看到这篇文章的底部是我们的文章评论列表，有别人评论的也有自己回复的。用同样的方式，F12查看源代码，或者查看元素定位到评论内容，如下所示：

这时我们点击进去查看相应的js文件，去看看能不能找到我们想要的东西：

哎呀，很不小心就被我发现了我想要的东西：

从上面我们可以分析出，获取文章的评论列表需要请求类似以下的地址：

"http://blog.csdn.net/wwj_748/comment/list/39726051?page=1，刚开始小巫并不知道这样的请求地址，是通过以上的方式才得知的。我们请求一篇文章需要知道对应文章的filename和pageIndex,然后以下面这种形式拼接：

/**
* 返回博文评论列表链接
*
* @param filename
*            文件名
* @param pageIndex
*            页数
* @return
*/
public static String getCommentListURL(String filename, String pageIndex) {
return "http://blog.csdn.net/wwj_748/comment/list/" + filename
+ "?page=" + pageIndex;
}

到了这一步基本上解决了最麻烦的事情，下面是业务逻辑的实现：

/BlogClient/src/com/xiaowu/blogclient/BlogCommentActivity.java

package com.xiaowu.blogclient;

import java.util.List;

import me.maxwin.view.IXListViewLoadMore;
import me.maxwin.view.IXListViewRefreshListener;
import me.maxwin.view.XListView;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import com.xiaowu.blogclient.adapter.CommentAdapter;
import com.xiaowu.blogclient.model.Comment;
import com.xiaowu.blogclient.model.Page;
import com.xiaowu.blogclient.util.Constants;
import com.xiaowu.blogclient.util.DateUtil;
import com.xiaowu.blogclient.util.HttpUtil;
import com.xiaowu.blogclient.util.JsoupUtil;
import com.xiaowu.blogclient.util.URLUtil;

/**
* 2014/8/13
*
* 博客评论列表
*
* @author wwj_748
*
*/
public class BlogCommentActivity extends Activity implements
IXListViewRefreshListener, IXListViewLoadMore {

private XListView listView;
private CommentAdapter adapter;

private ProgressBar progressBar;
private ImageView reLoadImageView;

private ImageView backBtn;
private TextView commentTV;

public static String commentCount = "";
private Page page;
private String filename;
private int pageIndex = 1;
private int pageSize = 20;

@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_comment);

init();
initComponent();

listView.setRefreshTime(DateUtil.getDate()); // 设置刷新时间
listView.startRefresh(); // 开始刷新
}

// 初始化
private void init() {
filename = getIntent().getExtras().getString("filename"); // 获得文件名
page = new Page();
adapter = new CommentAdapter(this);
}

// 初始化组件
private void initComponent() {
progressBar = (ProgressBar) findViewById(R.id.newsContentPro);
reLoadImageView = (ImageView) findViewById(R.id.reLoadImage);
reLoadImageView.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
System.out.println("click");
reLoadImageView.setVisibility(View.INVISIBLE);
progressBar.setVisibility(View.VISIBLE);
new MainTask().execute(Constants.DEF_TASK_TYPE.REFRESH);
}
});

backBtn = (ImageView) findViewById(R.id.backBtn);
backBtn.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
finish();
}
});
commentTV = (TextView) findViewById(R.id.comment);

listView = (XListView) findViewById(R.id.listview);
listView.setAdapter(adapter);
listView.setPullRefreshEnable(this);
listView.setPullLoadEnable(this);
listView.setOnItemClickListener(new OnItemClickListener() {

@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {

}
});

}

@Override
public void finish() {
super.finish();
// 退出动画
overridePendingTransition(R.anim.push_no, R.anim.push_right_out);
}

private class MainTask extends AsyncTask<String, Void, Integer> {

@Override
protected Integer doInBackground(String... params) {
// 获得返回json字符串
String temp = HttpUtil.httpGet(URLUtil.getCommentListURL(filename,
page.getCurrentPage()));
if (temp == null) {
return Constants.DEF_RESULT_CODE.ERROR;
}
// 获得评论列表
List<Comment> list = JsoupUtil.getBlogCommentList(temp,
Integer.valueOf(page.getCurrentPage()), pageSize);
if (list.size() == 0) {
return Constants.DEF_RESULT_CODE.NO_DATA;
}

if (params[0].equals(Constants.DEF_TASK_TYPE.LOAD)) {
adapter.addList(list);
return Constants.DEF_RESULT_CODE.LOAD;
} else {
adapter.setList(list);
return Constants.DEF_RESULT_CODE.REFRESH;
}
}

@Override
protected void onPostExecute(Integer result) {
if (result == Constants.DEF_RESULT_CODE.ERROR) {
Toast.makeText(getApplicationContext(), "网络信号不佳",
Toast.LENGTH_SHORT).show();
listView.stopRefresh(DateUtil.getDate());
listView.stopLoadMore();
reLoadImageView.setVisibility(View.VISIBLE);
} else if (result == Constants.DEF_RESULT_CODE.NO_DATA) {
Toast.makeText(getApplicationContext(), "无更多评论",
Toast.LENGTH_SHORT).show();
listView.stopLoadMore();
listView.stopRefresh(DateUtil.getDate());
commentTV.setText("共有评论：" + commentCount);
} else if (result == Constants.DEF_RESULT_CODE.LOAD) {
page.addPage();
pageIndex++;
adapter.notifyDataSetChanged();
listView.stopLoadMore();
} else if (result == Constants.DEF_RESULT_CODE.REFRESH) {
adapter.notifyDataSetChanged();
listView.stopRefresh(DateUtil.getDate());
page.setPage(2);
commentTV.setText("共有评论：" + commentCount); // 显示评论数
}
progressBar.setVisibility(View.INVISIBLE);
super.onPostExecute(result);
}
}

// 加载更多
@Override
public void onLoadMore() {
new MainTask().execute(Constants.DEF_TASK_TYPE.LOAD);
}

// 刷新评论
@Override
public void onRefresh() {
page.setPage(1);
new MainTask().execute(Constants.DEF_TASK_TYPE.REFRESH);
}
}

/BlogClient/src/com/xiaowu/blogclient/adapter/CommentAdapter.java

package com.xiaowu.blogclient.adapter;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Bitmap;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import com.xiaowu.blogclient.R;
import com.xiaowu.blogclient.model.Comment;
import com.xiaowu.blogclient.util.Constants;

/**
* 评论列表适配器
*
* @author wwj_748
*
*/
public class CommentAdapter extends BaseAdapter {
private ViewHolder holder;
private LayoutInflater layoutInflater;
private Context context;
private List<Comment> list;

private SpannableStringBuilder htmlSpannable;

private ImageLoader imageLoader = ImageLoader.getInstance();
private DisplayImageOptions options;

private String replyText;

public CommentAdapter(Context c) {
super();
layoutInflater = (LayoutInflater) LayoutInflater.from(c);
list = new ArrayList<Comment>();

imageLoader.init(ImageLoaderConfiguration.createDefault(c));
options = new DisplayImageOptions.Builder()
.showStubImage(R.drawable.csdn)
.showImageForEmptyUri(R.drawable.csdn)
.showImageOnFail(R.drawable.csdn)
.cacheInMemory().cacheOnDisc()
.imageScaleType(ImageScaleType.EXACTLY)
.bitmapConfig(Bitmap.Config.RGB_565)
.displayer(new FadeInBitmapDisplayer(300)).build();
}

public void setList(List<Comment> list) {
this.list = list;
}

public void addList(List<Comment> list) {
this.list.addAll(list);
}

public void clearList() {
this.list.clear();
}

public List<Comment> getList() {
return list;
}

public void removeItem(int position) {
if (list.size() > 0) {
list.remove(position);
}
}

@Override
public int getCount() {
return list.size();
}

@Override
public Object getItem(int position) {
return list.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
Comment item = list.get(position); // 获取评论项
if (null == convertView) {
holder = new ViewHolder();
switch (item.getType()) {
case Constants.DEF_COMMENT_TYPE.PARENT: // 父项
convertView = layoutInflater.inflate(R.layout.comment_item,
null);
holder.name = (TextView) convertView.findViewById(R.id.name);
holder.content = (TextView) convertView
.findViewById(R.id.content);
holder.date = (TextView) convertView.findViewById(R.id.date);
holder.reply = (TextView) convertView
.findViewById(R.id.replyCount);
holder.userface = (ImageView) convertView.findViewById(R.id.userface);

break;
case Constants.DEF_COMMENT_TYPE.CHILD: // 子项
convertView = layoutInflater.inflate(
R.layout.comment_child_item, null);
holder.name = (TextView) convertView.findViewById(R.id.name);
holder.content = (TextView) convertView
.findViewById(R.id.content);
holder.date = (TextView) convertView.findViewById(R.id.date);
break;
}
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}

if (null != item) {
switch (item.getType()) {
case Constants.DEF_COMMENT_TYPE.PARENT: // 主题项
holder.name.setText(item.getUsername());
holder.content.setText(Html.fromHtml(item.getContent())); // 显示评论内容
holder.date.setText(item.getPostTime());
//              holder.reply.setText(item.getReplyCount());
imageLoader.displayImage(item.getUserface(), holder.userface, options);// 显示头像
break;
case Constants.DEF_COMMENT_TYPE.CHILD: // 回复项
holder.name.setText(item.getUsername());
replyText = item.getContent().replace("[reply]", "【");
replyText = replyText.replace("[/reply]", "】");
holder.content.setText(Html.fromHtml(replyText));
holder.date.setText(item.getPostTime());
break;
default:
break;
}
}
return convertView;
}

@Override
public int getViewTypeCount() {
return 2;
}

@Override
public int getItemViewType(int position) {
switch (list.get(position).getType()) {
case Constants.DEF_COMMENT_TYPE.PARENT: // 父节点
return 0;
case Constants.DEF_COMMENT_TYPE.CHILD: // 子节点
return 1;
}
return 1;
}

@Override
public boolean isEnabled(int position) {
return true;
}

private class ViewHolder {
TextView id;
TextView date;
TextView name;
TextView content;
ImageView userface;
TextView reply;
}
}

最终效果图如下：

最后：

关于小巫CSDN博客客户端的开发基本上都介绍完了，更多详细的实现请到以下链接下载源码查看：

http://download.csdn.net/detail/wwj_748/7912513

Android应用开发-小巫CSDN博客客户端总结篇

2014博客之星投票地址：http://vote.blog.csdn.net/blogstar2014/details?username=wwj_748#content

小巫CSDN博客客户端的开发要告一段落了，这个作品已经成功在360、应用宝渠道上线，有兴趣的朋友可以到以下地址下载：

360手机助手：http://zhushou.360.cn/detail/index/soft_id/1973215?recrefer=SE_D_%E5%B0%8F%E5%B7%ABCSDN%E5%8D%9A%E5%AE%A2#prev

应用宝：http://android.myapp.com/myapp/detail.htm?apkName=com.xiaowu.blogclient

本篇博客作为此系列博客的总结篇，是想把自己开发这款应用中所获得的一些经验分享给大家以表感谢那么多朋友对小巫的关注和支持。

很多初学者学习Android的时候总会有这种感觉，很想做点什么东西出来，但就是无从下手，也有很多人请教我说如何去学Android，如何去开发一款应用之类的问题。一般我都只是简单的回答是，从简单做起，先把基础掌握之后，然后尝试去实现自己的想法，不要一下子就想把所有细节都想透了，把所有功能都实现了，因为这是不可能的。初学者最怕的就是急躁，想当初我学Android的时候也有过这样的经历，也是从菜鸟过来的，但现在是否成为你们口中的大牛呢，我也不敢说是。因为我自己也有很多不知道不了解的东西，关于更深层次的东西我也还有很多没有涉及到，但是我却可以实现自己的想法，开发自己的产品。从我的博客，大家可以看到小巫开发过以下应用：

博客开源的有以下：

1. 简美音乐播放器（一款简单入门的音乐播放器）

2. 小巫新闻客户端（新闻类客户端开发，包含客户端、服务端开发）

3. 浪腾微博客户端（新浪微博、腾讯微博整合客户端开发，开放平台API调用）

4. 小巫CSDN博客客户端（CSDN手机客户端，分析html网页，解析html）

我也发表很多关于这些客户端的开发，相信大家都可以知道开发一款应用并不是想象得那么难，难的是没有动手之前就被前面的不知所措给阻碍了，所以我建议一些初学者，无须害怕自己没有掌握相关的知识，百度是你最好的老师，遇到不懂的，或者不明白的可以自己尝试去寻找答案，网上很多大牛都很有分享精神，会把一些经验分享给大家，这时你就可以通过学习他们的博文或者请教他们来得到解决方案。

我总结了开发这样一款应用需要的一些能力：

- 最基本的Java编程（开发Android应用需要有最基本的Java编程基础和面向对象思维）

- 基础的Android开发知识（包括UI设计，基本控件的使用，4大组件、数据存储、网络请求）

- 搜索解决方案的能力（关键字搜索问题，熟练使用搜索引擎寻找答案）

- 解决问题的能力（如何查看logcat、debug还有定位问题）

- 接入SDK的能力（接入第三方服务，熟悉如何快速集成到自己应用当中）

- 经验总结的能力（善于总结分析问题，类似写文档或者博客记录问题）

关于应用，UI可能一开始不能设计得很好看，因为我们开发者大部分都不太会设计，但这没有关系，有很多应用可供我们参考，小巫的一些素材都是通过自己搜集，然后应用到自己的程序中，可以解压缩apk包，获取里面的素材，如果有能力的，也可以自己使用photoShop设计相应的素材或者寻找美工帮忙设计。

开始一款应用的开发，需要自己想清楚整个程序的框架，大致已什么样的方向去设计，专业点来说就是需求分析，先把需求确定下来之后，确定基本的交互页面，把整个框架搭建起来之后，就可以开始考虑业务逻辑的实现，比如如何通过网络请求获取数据，以什么样的形式展示数据等。当整体功能需求实现之后，如果有能力的可以尝试优化程序，比如ListView的优化，性能相关的优化，UI界面的优化等等，这些都是属于你在学习过程中需要不断积累的东西。

关于小巫CSDN博客客户端的总结大概就这么多，如果各位有其他想跟笔者讨论交流的，可以及时留言，我可以在博客中继续补充。这里预告笔者近期在开发的一款产品，相信不久之后也会跟大家见面，希望能跟大家一起进步，谢谢。