首先声明一点,这里写的东西只是自己记录笔记使用的东西而已,我希望的是学习人家的架构和编程经历,不会对osc客户端进行改造什么的,当然我会有自己的看法,但是经验要有限,烦请指正.
OSC的主界面和大部分的新闻客户端的主界面是相似的,也使用了webview,同样也是使用了loadDataWithBaseURL这个函数来load String.
在这里还会介绍一下其他小知识:
未读评论提示,
超链接的处理,点击webview中的图片可以查看详细图片
1.整体功能介绍
先是获取数据,这个功能在initData函数中,请求的流程和第一篇的大体一致,这里就不再做介绍
这里着重介绍的是获取数据后对界面显示的处理 是在handler的handleMessage成员函数中
mHandler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == 1) {
headButtonSwitch(DATA_LOAD_COMPLETE);
mTitle.setText(newsDetail.getTitle());
mAuthor.setText(newsDetail.getAuthor());
mPubDate.setText(StringUtils.friendly_time(newsDetail
.getPubDate()));
mCommentCount.setText(String.valueOf(newsDetail
.getCommentCount()));
// 是否收藏
if (newsDetail.getFavorite() == 1)
mFavorite
.setImageResource(R.drawable.widget_bar_favorite2);
else
mFavorite
.setImageResource(R.drawable.widget_bar_favorite);
// 显示评论数
if (newsDetail.getCommentCount() > 0) {
bv_comment.setText(newsDetail.getCommentCount() + "");
bv_comment.show();
} else {
bv_comment.setText("");
bv_comment.hide();
}
String body = UIHelper.WEB_STYLE + newsDetail.getBody();
// 读取用户设置:是否加载文章图片--默认有wifi下始终加载图片
boolean isLoadImage;
AppContext ac = (AppContext) getApplication();
if (AppContext.NETTYPE_WIFI == ac.getNetworkType()) {
isLoadImage = true;
} else {
isLoadImage = ac.isLoadImage();
}
if (isLoadImage) {
// 过滤掉 img标签的width,height属性
body = body.replaceAll(
"(<img[^>]*?)\\s+width\\s*=\\s*\\S+", "$1");
body = body.replaceAll(
"(<img[^>]*?)\\s+height\\s*=\\s*\\S+", "$1");
// 添加点击图片放大支持
body = body.replaceAll("(<img[^>]+src=\")(\\S+)\"",
"$1$2\" onClick=\"javascript:mWebViewImageListener.onImageClick('$2')\"");
} else {
// 过滤掉 img标签
body = body.replaceAll("<\\s*img\\s+([^>]*)\\s*>", "");
}
// 更多关于***软件的信息
String softwareName = newsDetail.getSoftwareName();
String softwareLink = newsDetail.getSoftwareLink();
if (!StringUtils.isEmpty(softwareName)
&& !StringUtils.isEmpty(softwareLink))
body += String
.format("<div id='oschina_software' style='margin-top:8px;color:#FF0000;font-weight:bold'>更多关于: <a href='%s'>%s</a> 的详细信息</div>",
softwareLink, softwareName);
// 相关新闻
if (newsDetail.getRelatives().size() > 0) {
String strRelative = "";
for (Relative relative : newsDetail.getRelatives()) {
strRelative += String
.format("<a href='%s' style='text-decoration:none'>%s</a><p/>",
relative.url, relative.title);
}
body += String.format(
"<p/><hr/><b>相关资讯</b><div><p/>%s</div>",
strRelative);
}
body += "<div style='margin-bottom: 80px'/>";
System.out.println(body);
mWebView.loadDataWithBaseURL(null, body, "text/html",
"utf-8", null);
mWebView.setWebViewClient(UIHelper.getWebViewClient());
// 发送通知广播
if (msg.obj != null) {
UIHelper.sendBroadCast(NewsDetail.this,
(Notice) msg.obj);
}
} else if (msg.what == 0) {
headButtonSwitch(DATA_LOAD_FAIL);
UIHelper.ToastMessage(NewsDetail.this,
R.string.msg_load_is_null);
} else if (msg.what == -1 && msg.obj != null) {
headButtonSwitch(DATA_LOAD_FAIL);
((AppException) msg.obj).makeToast(NewsDetail.this);
}
}
};
①在html中添加一个Style 确定默认的html风格
String body = UIHelper.WEB_STYLE + newsDetail.getBody();
这里getBody函数获取的将会是新闻内容的html代码,服务器返回的也是这个代码
②读取加载图片开关,如果是wifi默认会加载图片 如果设置了不能加载图片就把img标签替换为空
如果加载图片还会做两个事情 过滤width 和height 属性 并且添加onClick的js函数,实现稍后介绍
boolean isLoadImage;
AppContext ac = (AppContext) getApplication();
if (AppContext.NETTYPE_WIFI == ac.getNetworkType()) {
isLoadImage = true;
} else {
isLoadImage = ac.isLoadImage();
}
if (isLoadImage) {
// 过滤掉 img标签的width,height属性
body = body.replaceAll(
"(<img[^>]*?)\\s+width\\s*=\\s*\\S+", "$1");
body = body.replaceAll(
"(<img[^>]*?)\\s+height\\s*=\\s*\\S+", "$1");
// 添加点击图片放大支持
body = body.replaceAll("(<img[^>]+src=\")(\\S+)\"",
"$1$2\" onClick=\"javascript:mWebViewImageListener.onImageClick('$2')\"");
} else {
// 过滤掉 img标签
body = body.replaceAll("<\\s*img\\s+([^>]*)\\s*>", "");
}
然后还会出来相关资讯等信息 也是替换字符串
③接着会把加工后的新闻内容显示到webview中去
mWebView.loadDataWithBaseURL(null, body, "text/html","utf-8", null);
④ 我们知道,这个新闻页面同样需要处理超链接等数据,这里由于使用的是webview,所以和前面textview处理超链接有些不同但是更加简单,我们知道Webview有个setWebViewClient函数可以处理超链接跳转等功能,oscapp也是使用了这个方法:
mWebView.setWebViewClient(UIHelper.getWebViewClient());
那么看一下getWebViewClient()这个函数做了什么:
public static WebViewClient getWebViewClient() {
return new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
showUrlRedirect(view.getContext(), url);
return true;
}
};
}
showUrlRedirect函数的处理
/**
* url跳转
*
* @param context
* @param url
*/
public static void showUrlRedirect(Context context, String url) {
URLs urls = URLs.parseURL(url);
if (urls != null) {
showLinkRedirect(context, urls.getObjType(), urls.getObjId(),
urls.getObjKey());
} else {
openBrowser(context, url);
}
}
这里只分析showLinkRedirect方法
public static void showLinkRedirect(Context context, int objType,
int objId, String objKey) {
switch (objType) {
case URLs.URL_OBJ_TYPE_NEWS:
showNewsDetail(context, objId);
break;
case URLs.URL_OBJ_TYPE_QUESTION:
showQuestionDetail(context, objId);
break;
case URLs.URL_OBJ_TYPE_QUESTION_TAG:
showQuestionListByTag(context, objKey);
break;
case URLs.URL_OBJ_TYPE_SOFTWARE:
showSoftwareDetail(context, objKey);
break;
case URLs.URL_OBJ_TYPE_ZONE:
showUserCenter(context, objId, objKey);
break;
case URLs.URL_OBJ_TYPE_TWEET:
showTweetDetail(context, objId);
break;
case URLs.URL_OBJ_TYPE_BLOG:
showBlogDetail(context, objId);
break;
case URLs.URL_OBJ_TYPE_OTHER:
openBrowser(context, objKey);
break;
}
}
那么我们发现整体的思路和之前我们设想的是一样的,更加WebViewClient中shouldOverrideUrlLoading函数获取到的url参数进行判断然后进行跳转.
URLs urls = URLs.parseURL(url);这句话其实就是解析url为两个参数:链接类型,和对应新闻条目的ID 这样就可以跳转到相应页面的相应条目了
至于最后一句发送广播在后面章节中还会有介绍
⑤让我们回到前面,新闻详细页面还有一个功能,点击图片显示大图,可以做两点放大缩小等操作等等,也是使用了网上的一个代码,具体原理是使用matrix 缩放图片的bitmap
我们关注的还是图片如何响应点击事件:
前面我们已经个image添加了点击事件:
body = body.replaceAll("(<img[^>]+src=\")(\\S+)\"",
"$1$2\" onClick=\"javascript:mWebViewImageListener.onImageClick('$2')\"");
那么我们如何使用呢?
其实在WebView初始化的事情注册了响应这个onclick 的事件(可以这么说吗?):
UIHelper.addWebImageShow(this, mWebView);
这个注册js函数的实现:
/**
* 添加网页的点击图片展示支持
*/
@SuppressLint("SetJavaScriptEnabled")
public static void addWebImageShow(final Context cxt, WebView wv) {
wv.getSettings().setJavaScriptEnabled(true);
wv.addJavascriptInterface(new OnWebViewImageListener() {
@Override
public void onImageClick(String bigImageUrl) {
if (bigImageUrl != null)
UIHelper.showImageZoomDialog(cxt, bigImageUrl);
}
}, "mWebViewImageListener");
}
showImageZoomdialog就是打开查看图片的activity了.
2.其他小知识点介绍
①对于评论的未读条目数显示,这里作者使用了一个开源的控件,
用法如下
bv_comment = new BadgeView(this, mCommentList);
bv_comment.setBackgroundResource(R.drawable.widget_count_bg2);
bv_comment.setIncludeFontPadding(false);
bv_comment.setGravity(Gravity.CENTER);
bv_comment.setTextSize(8f);
bv_comment.setTextColor(Color.WHITE);
以本案例为例分析,大体的原理如下:
获取到评论imageview的 parent
new 一个framelayout然后把这个imageview和新new出来的气泡view添加到framelayout中
把imageview从parent中remove掉 把这个framelayout添加到parent中
这样就显示这个未读提示气泡了
②还有一个小细节是收藏的设计
点击收藏会启动一个线程 请求服务器处理,如果收藏成功,在修改UI的同事也设置内存缓存中的fav 更改,并重新序列化到本地 这样的处理逻辑还是不错的,这里我们就又看到了getCacheKey的用法 代码如下:
final Handler handler = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == 1) {
Result res = (Result) msg.obj;
if (res.OK()) {
if (newsDetail.getFavorite() == 1) {
newsDetail.setFavorite(0);
mFavorite
.setImageResource(R.drawable.widget_bar_favorite);
} else {
newsDetail.setFavorite(1);
mFavorite
.setImageResource(R.drawable.widget_bar_favorite2);
}
// 重新保存缓存
ac.saveObject(newsDetail, newsDetail.getCacheKey());
}
UIHelper.ToastMessage(NewsDetail.this,
res.getErrorMessage());
} else {
((AppException) msg.obj).makeToast(NewsDetail.this);
}
}
};
new Thread() {
public void run() {
Message msg = new Message();
Result res = null;
try {
if (newsDetail.getFavorite() == 1) {
res = ac.delFavorite(uid, newsId,
FavoriteList.TYPE_NEWS);
} else {
res = ac.addFavorite(uid, newsId,
FavoriteList.TYPE_NEWS);
}
msg.what = 1;
msg.obj = res;
} catch (AppException e) {
e.printStackTrace();
msg.what = -1;
msg.obj = e;
}
handler.sendMessage(msg);
}
}.start();
}
③对于评论和新闻内容的切换 使用了一个 以前没有用过的控件ViewSwitch 其实用法也是很简单,可以省去一堆gone 了
/**
* 底部栏切换
*
* @param type
*/
private void viewSwitch(int type) {
switch (type) {
case VIEWSWITCH_TYPE_DETAIL:
mDetail.setEnabled(false);
mCommentList.setEnabled(true);
mHeadTitle.setText(R.string.news_detail_head_title);
mViewSwitcher.setDisplayedChild(0);
break;
case VIEWSWITCH_TYPE_COMMENTS:
mDetail.setEnabled(true);
mCommentList.setEnabled(false);
mHeadTitle.setText(R.string.comment_list_head_title);
mViewSwitcher.setDisplayedChild(1);
break;
}
}
④双击全屏使用了GestureDetector:
/**
* 注册双击全屏事件
*/
private void regOnDoubleEvent() {
gd = new GestureDetector(this,
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
isFullScreen = !isFullScreen;
if (!isFullScreen) {
WindowManager.LayoutParams params = getWindow()
.getAttributes();
params.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().setAttributes(params);
getWindow()
.clearFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
mHeader.setVisibility(View.VISIBLE);
mFooter.setVisibility(View.VISIBLE);
} else {
WindowManager.LayoutParams params = getWindow()
.getAttributes();
params.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
getWindow().setAttributes(params);
getWindow()
.addFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
mHeader.setVisibility(View.GONE);
mFooter.setVisibility(View.GONE);
}
return true;
}
});
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (isAllowFullScreen()) {
gd.onTouchEvent(event);
}
return super.dispatchTouchEvent(event);
}