Xamarin Android 打造属于自己的博客园APP(4)

前言

今天早上终于把本地的Git环境搞好,然后顺利的丢到了Github上,主要是我现在用的电脑之前是其它同事用的 ,是他的Git账号,切换成我自己的用了太长时间了,也主要是以前没有咋用过Git命令。

先说地址吧:https://github.com/HuUncle/Xamarin-Android-CNBlog,觉得还可以的朋友可以点个星,毕竟我是个小菜鸟,写个APP不容易啊。

大概说下项目结构吧,整体分为两层,一个XamarinAndroid工程,一个可移植项目库,里面封装的博客园的API请求,打算在写IOS客户端的时候移植过去。

image_thumb5

接口请求的话,全是用的异步在请求,毕竟是APP,总不能UI卡死吧。

CNBlog功能描述:

已实现功能:

1.博客园账号登录

2.分页获取首页文章,分页精华文章,分页知识库文章,分页新闻

3.获取文章、新闻评论列表,发送评论

4.文章、新闻在线收藏、分享

5.查看我的博客,查看我的收藏

6.搜索博主

APP页面效果:

1.主页:

_20161215_174916_thumb1

2.文章详情:

_20161215_175625_thumb1

3.评论列表:

_20161215_175727_thumb1

4.个人中心:

_20161215_175801_thumb1

5.搜索博主:

_20161215_175825_thumb1

 

截图好累,更多效果的话,后面给出APK下载地址,各位自己下载安装体验吧。

主页的效果是用的ViewPager+TabLayout实现的滑动效果,也是一种很常用的APP布局方式,贴出关键代码:

布局文件:main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical">
    <LinearLayout
        android:paddingTop="10dp"
        android:layout_gravity="center_vertical"
        android:background="@color/title_bg"
        android:paddingBottom="10dp"
        android:layout_height="wrap_content"
        android:layout_width="match_parent">
        <Button
            android:layout_width="28dp"
            android:layout_height="28dp"
            android:layout_marginBottom="5dp"
            android:layout_marginTop="5dp"
            android:background="@drawable/titlebar_menu_selector"
            android:id="@+id/title_bar_left_menu"
            android:layout_gravity="left|center_vertical"
            android:layout_marginLeft="10dp" />
        <TextView
            android:layout_height="match_parent"
            android:gravity="center_vertical"
            android:textSize="22sp"
            android:textColor="@android:color/white"
            android:layout_width="wrap_content"
            android:layout_marginLeft="25dp"
            android:text="博客园" />
        <LinearLayout
            android:layout_height="match_parent"
            android:gravity="right"
            android:paddingRight="10dp"
            android:layout_width="match_parent">
            <Button
                android:layout_width="28dp"
                android:layout_height="28dp"
                android:id="@+id/btn_blogger_search"
                android:layout_marginBottom="5dp"
                android:layout_marginTop="5dp"
                android:background="@drawable/titlebar_search_selector"
                android:layout_gravity="left|center_vertical" />
            <Button
                android:layout_width="28dp"
                android:layout_height="28dp"
                android:layout_marginBottom="5dp"
                android:layout_marginTop="5dp"
                android:background="@drawable/titlebar_more_selector"
                android:layout_gravity="left|center_vertical"
                android:layout_marginLeft="15dp" />
        </LinearLayout>
    </LinearLayout>
<!--app:tabMode="scrollable"  这个属性我在代码中设置了-->
<!-- tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);-->
    <android.support.design.widget.TabLayout
        android:id="@+id/sliding_tabs"
        style="@style/MyCustomTabLayout"
        android:background="@android:color/white"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="0px"
        android:layout_weight="1"
        android:background="@android:color/white" />
</LinearLayout>

ViewPager适配器:SimpleFragmentPagerAdapter.cs

using Android.Content;
using Android.Views;
using Android.Widget;
using Java.Lang;
using Android.Support.V4.App;
using Android.Text;
using CNBlog.Droid.Activities;

namespace CNBlog.Droid
{
    public class SimpleFragmentPagerAdapter : FragmentPagerAdapter
    {
        int PAGE_COUNT = 4;
        private string [] tabTitles = new string[] { "首页","精华", "新闻", "知识库"};
        private Context context;
        public SimpleFragmentPagerAdapter(Android.Support.V4.App.FragmentManager fm, Context context)
            :base(fm)
        {
            this.context = context;
            
        }

        public override int Count
        {
            get
            {
                return PAGE_COUNT;
            }
        }

        public override Android.Support.V4.App.Fragment GetItem(int position)
        {
            switch (position)
            {
                case 0:
                    return new IndexPageFragment();
                case 1:
                    return new EssencePageFragement();
                case 2:
                    return new NewsPageFragment();
                case 3:
                    return new RecommendPageFragment();
                default:
                    return new NewsPageFragment();
            }
        }

        public override ICharSequence GetPageTitleFormatted(int position)
        {
            SpannableString sb = new SpannableString(tabTitles[position]);
            return sb;
        }

        public View GetTabView(int position)
        {
            View view = LayoutInflater.From(context).Inflate(Resource.Layout.tab_item, null);
            TextView tv = (TextView)view.FindViewById(Resource.Id.textView);
            tv.Text = tabTitles[position];
            //ImageView img = (ImageView)view.FindViewById(Resource.Id.imageView);
            //img.SetImageResource(Resource.Mipmap.Icon);//设置tab的图标
            return view;
        }

    }
}

然后4个Tab由四个不同的Fragment分别去维护。

实际开发过程中,遇到个有趣的问题,在主页的时候,当前页面如果在第一页的时候,依次往右滑动的话,后面的页面竟然都没有刷新就把文章新闻给获取出来了,如果从第一个页面直接点到第三个页面的话,页面就有刷新效果。这就相当interesting了ZQITF36LRQ7GMGBJPYSV_thumb

 

然后上网搜索了下Viewpager的用法,发现Viewpager是有缓存机制的,ViewPager切换页面时默认情况下非相邻的页面会被销毁掉(ViewPager默认缓存相邻的页面以便快速切换),

可以通过设置viewPager.OffscreenPageLimit的属性设置缓存页数。But我没有设置数量为4,我不需要一次性缓存这么多,不仅浪费内存不说,也许用户可能只是首页看了一篇文章就退了,而且博客园接口调用也很快,而且CNBlog都是基于异步的,页面很流畅的,所以不需要一次性缓存那么多页面。

 

ListView异步网络图片加载

相信大家平时做Android应用的时候,多少会接触到异步加载图片,或者加载大量图片的问题,而加载图片我们常常会遇到许多的问题,比如说图片的错乱,OOM等问题。

在CNBlog里头显示图片最多的是用户头像,

起初是我自己写的异步去下载图片,下载完成以后将图片缓存到本地,将图片名称命名成图片下载地址,当Listview滑动加载图片时,先判断本地是否已存在,如果存在就从本地加载,没有则从网络下载,但是问题来了,有时候会发现异步下载图片的时候会错位,经过初步分析应该是异步加载图片的时候,然后listview又重用了 convertview导致的图片错位。

贴上一个园友写的文章,分析的很有道理:http://www.cnblogs.com/lesliefang/p/3619223.html

大致的解决思路是给ImageView设置一个tag,可以设置为图片下载地址,设置Imageview的图片时,比对tag是否和当前下载路径是否一致。

这个问题解决了,oom又来了,频繁下载,频繁从本地读取都是造成oom的原因。这就尴尬了,心想肯定不止自己遇到过这个问题的,果断上网一搜,android原生有一个叫做Universal-Image-Loader的框架,一看介绍,有这么多功能

  1. 多线程下载图片,图片可以来源于网络,文件系统,项目文件夹assets中以及drawable中等
  2. 支持随意的配置ImageLoader,例如线程池,图片下载器,内存缓存策略,硬盘缓存策略,图片显示选项以及其他的一些配置
  3. 支持图片的内存缓存,文件系统缓存或者SD卡缓存
  4. 支持图片下载过程的监听
  5. 根据控件(ImageView)的大小对Bitmap进行裁剪,减少Bitmap占用过多的内存
  6. 较好的控制图片的加载过程,例如暂停图片加载,重新开始加载图片,一般使用在ListView,GridView中,滑动过程中暂停加载图片,停止滑动的时候去加载图片
  7. 提供在较慢的网络下对图片进行加载

然后上github搜索了下关于imageloader,然后就找到了一个绑定好的imageloader库,一句话,前人种树,后人乘凉。

https://github.com/LukeForder/Xamarin-Bindings-Android-Universal-Image-Loader

用法的话,也基本和Java的一致,CNBlog里头也有实现,大家有兴趣的话可以看一下。

贴上关键代码:

BlogApplication.InitImageLoader(this.Context);
ImageLoader imgLoader;= ImageLoader.Instance;
DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder()
                                       .ShowImageForEmptyUri(Resource.Drawable.girl)//空URL显示图片
                                       .ShowImageOnFail(Resource.Drawable.girl)//加载失败显示图片
                                       .ShowImageOnLoading(Resource.Drawable.girl)//正在加载显示图片
                                       .CacheInMemory(true)//缓存到内存
                                       .CacheOnDisk(true)//缓存到SD卡
                                       .ResetViewBeforeLoading()
                                       .Build();
imgLoader.DisplayImage(item.Avatar, imgView, displayImageOptions);

imgLoader.DisplayImage(“图片下载地址”,”ImageVIew”,”ImageLoader图片显示配置”);

文章详情

刚开始的纠结了很久如何显示文章详情,因为文章内容是带标签的

image

要在手机上显示,而且还要美观,这真是个big problem。

这里采用的方案是,用webView来显示文章内容。做法是首先在assets新建了一个Html文件,编码css,用于排版文章内容。

html代码:

<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<script>
/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript */
var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function(e){var t=n.util.type(e);switch(t){case"Object":var a={};for(var r in e)e.hasOwnProperty(r)&&(a[r]=n.util.clone(e[r]));return a;case"Array":return e.map&&e.map(function(e){return n.util.clone(e)})}return e}},languages:{extend:function(e,t){var a=n.util.clone(n.languages[e]);for(var r in t)a[r]=t[r];return a},insertBefore:function(e,t,a,r){r=r||n.languages;var i=r[e];if(2==arguments.length){a=arguments[1];for(var l in a)a.hasOwnProperty(l)&&(i[l]=a[l]);return i}var o={};for(var s in i)if(i.hasOwnProperty(s)){if(s==t)for(var l in a)a.hasOwnProperty(l)&&(o[l]=a[l]);o[s]=i[s]}return n.languages.DFS(n.languages,function(t,n){n===r[e]&&t!=e&&(this[t]=o)}),r[e]=o},DFS:function(e,t,a,r){r=r||{};for(var i in e)e.hasOwnProperty(i)&&(t.call(e,i,e[i],a||i),"Object"!==n.util.type(e[i])||r[n.util.objId(e[i])]?"Array"!==n.util.type(e[i])||r[n.util.objId(e[i])]||(r[n.util.objId(e[i])]=!0,n.languages.DFS(e[i],t,i,r)):(r[n.util.objId(e[i])]=!0,n.languages.DFS(e[i],t,null,r)))}},plugins:{},highlightAll:function(e,t){var a={callback:t,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};n.hooks.run("before-highlightall",a);for(var r,i=a.elements||document.querySelectorAll(a.selector),l=0;r=i[l++];)n.highlightElement(r,e===!0,a.callback)},highlightElement:function(t,a,r){for(var i,l,o=t;o&&!e.test(o.className);)o=o.parentNode;o&&(i=(o.className.match(e)||[,""])[1].toLowerCase(),l=n.languages[i]),t.className=t.className.replace(e,"").replace(/\s+/g," ")+" language-"+i,o=t.parentNode,/pre/i.test(o.nodeName)&&(o.className=o.className.replace(e,"").replace(/\s+/g," ")+" language-"+i);var s=t.textContent,u={element:t,language:i,grammar:l,code:s};if(n.hooks.run("before-sanity-check",u),!u.code||!u.grammar)return n.hooks.run("complete",u),void 0;if(n.hooks.run("before-highlight",u),a&&_self.Worker){var c=new Worker(n.filename);c.onmessage=function(e){u.highlightedCode=e.data,n.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,r&&r.call(u.element),n.hooks.run("after-highlight",u),n.hooks.run("complete",u)},c.postMessage(JSON.stringify({language:u.language,code:u.code,immediateClose:!0}))}else u.highlightedCode=n.highlight(u.code,u.grammar,u.language),n.hooks.run("before-insert",u),u.element.innerHTML=u.highlightedCode,r&&r.call(t),n.hooks.run("after-highlight",u),n.hooks.run("complete",u)},highlight:function(e,t,r){var i=n.tokenize(e,t);return a.stringify(n.util.encode(i),r)},tokenize:function(e,t){var a=n.Token,r=[e],i=t.rest;if(i){for(var l in i)t[l]=i[l];delete t.rest}e:for(var l in t)if(t.hasOwnProperty(l)&&t[l]){var o=t[l];o="Array"===n.util.type(o)?o:[o];for(var s=0;s<o.length;++s){var u=o[s],c=u.inside,g=!!u.lookbehind,h=!!u.greedy,f=0,d=u.alias;if(h&&!u.pattern.global){var p=u.pattern.toString().match(/[imuy]*$/)[0];u.pattern=RegExp(u.pattern.source,p+"g")}u=u.pattern||u;for(var m=0,y=0;m<r.length;y+=(r[m].matchedStr||r[m]).length,++m){var v=r[m];if(r.length>e.length)break e;if(!(v instanceof a)){u.lastIndex=0;var b=u.exec(v),k=1;if(!b&&h&&m!=r.length-1){if(u.lastIndex=y,b=u.exec(e),!b)break;for(var w=b.index+(g?b[1].length:0),_=b.index+b[0].length,A=m,S=y,P=r.length;P>A&&_>S;++A)S+=(r[A].matchedStr||r[A]).length,w>=S&&(++m,y=S);if(r[m]instanceof a||r[A-1].greedy)continue;k=A-m,v=e.slice(y,S),b.index-=y}if(b){g&&(f=b[1].length);var w=b.index+f,b=b[0].slice(f),_=w+b.length,x=v.slice(0,w),O=v.slice(_),j=[m,k];x&&j.push(x);var N=new a(l,c?n.tokenize(b,c):b,d,b,h);j.push(N),O&&j.push(O),Array.prototype.splice.apply(r,j)}}}}}return r},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.matchedStr=a||null,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var i={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}n.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=(o?" ":"")+s+'="'+(i.attributes[s]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'"'+(o?" "+o:"")+">"+i.content+"</"+i.tag+">"},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,i=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),i&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,document.addEventListener&&!r.hasAttribute("data-manual")&&("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
Prism.languages.markup={comment:/<!--[\w\W]*?-->/,prolog:/<\?[\w\W]+?\?>/,doctype:/<!DOCTYPE[\w\W]+?>/,cdata:/<!\[CDATA\[[\w\W]*?]]>/i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&amp;/,"&"))}),Prism.languages.xml=Prism.languages.markup,Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup;
Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:{pattern:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,greedy:!0},property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/(<style[\w\W]*?>)[\w\W]*?(?=<\/style>)/i,lookbehind:!0,inside:Prism.languages.css,alias:"language-css"}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag));
Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/};
Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*\*?|\/|~|\^|%|\.{3}/}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^\/])\/(?!\/)(\[.+?]|\\.|[^\/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0,greedy:!0}}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\\\|\\?[^\\])*?`/,greedy:!0,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<script[\w\W]*?>)[\w\W]*?(?=<\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:"language-javascript"}}),Prism.languages.js=Prism.languages.javascript;
</script>
<style type="text/css">
      body{font-family:Helvetica,"Microsoft Yahei",Verdana,Helvetica,SimSun,Arial,"Arial Unicode MS",MingLiu,PMingLiu,"MS Gothic",sans-serief;margin:0;padding:0 8px;background-color:#efeff0;color:#333;word-wrap:break-word;background-color:#FFFFFF;}
p{margin-top:0;margin-bottom:5pt;line-height: 1.6em;}  
#header{text-align:center;background:transparent white repeat-x scroll center bottom; padding-top:6pt;margin-bottom:5pt;-webkit-background-size:320px 2px;}
#header h3{margin-bottom:0px; margin-top:5px;font-size:14pt;padding:0 5pt;color:#464646;line-height:1.3em;}
.describe{color:#8e8e8e;font-size:12pt;padding:4pt 0; color:#333;}
#info{ font-size:10pt;line-height:1.6; color:#787878;}
#content{ font-size:12pt;line-height:1.8;}
img{max-width:80%;height:auto;}
div.bimg{text-align:center;padding:0;}
.photo_title{font-weight:bold;font-size:14pt;margin-top:15px;}
.langs_cn{color:#006200;}
audio{width:100%}
*{-webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */
    /*-webkit-text-size-adjust: none;*/ /* prevent webkit from resizing text to fit */
    -webkit-tap-highlight-color: rgba(0,0,0,0.15); /* make transparent link selection, adjust last value opacity 0 to 1.0 */
    /*-webkit-user-select: none; /* prevent copy paste, to allow, change 'none' to 'text' */
}
@media screen and (-webkit-device-pixel-ratio: 2) {
    #header{background-image:transparent white repeat-x scroll center bottom;-webkit-background-size:320px 1px;}
}
#content{font-size:15px}
pre{border:solid 1px #cdcdcd;background:#f5f5f5;padding:15px 5px}

pre {word-break:normal; width:auto; display:block; white-space:pre-wrap;word-wrap : break-word ;overflow: hidden ;} 

pre {
  tab-width: 4;
            font-size:12px;
}
            
</style>
</head>
<body>

<div id="header">
    <h3>
        #title#
    </h3>

    <div class="describe"><p/>#author#<p/>

    <div id="info"><p/>#time#</div>
    </div>
</div>
        
<div id="content">
    #content#
</div>

</body>

</html>

然后替换html中的title、author、time、content 为实际内容,还加载了prismjs,用来美化code。

一些WebView的设置 ArticleDetailActivity:

using System;
using System.IO;
using Android.App;
using Android.Content;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Support.Design.Widget;
using Android.Util;
using Android.Views;
using Android.Webkit;
using Android.Widget;
using CNBlog.Droid.PullableView;
using CNBlog.Droid.Utils;
using CNBlogAPI.Model;
using CNBlogAPI.Service;
using Newtonsoft.Json;
using Msg = Sino.Droid.AppMsg;

namespace CNBlog.Droid.Activities
{
    [Activity(Label = "ArticleDetailActivity")]
    public class ArticleDetailActivity : Activity,OnRefreshListener
    {
        PullableWebView webView;
        PullToRefreshLayout ptrl;
        Article article;
        string content;
        Button btnViewComments;
        Button btnWriteComments;
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            SetContentView(Resource.Layout.article_detail);
            article = JsonConvert.DeserializeObject<Article>(Intent.GetStringExtra("current"));
            FindViewById<TextView>(Resource.Id.head_title).Text = "文章详情";
            Button btnBack = FindViewById<Button>(Resource.Id.title_bar_back);
            btnBack.Click+=delegate { Finish();};
            bindControls();
        }
        
        private async void bindControls()
        {
            btnWriteComments = FindViewById<Button>(Resource.Id.footbar_write_comment);
            btnWriteComments.Click+=delegate
            {
                BottomSheetDialog bottomSheetDiaolog = new BottomSheetDialog(this);
                var inflater = GetSystemService(Context.LayoutInflaterService) as LayoutInflater;
                EditText textComments = FindViewById<EditText>(Resource.Id.text_comments);
                View view = inflater.Inflate(Resource.Layout.write_comments, null);
                TextView textCancel = view.FindViewById<TextView>(Resource.Id.text_cancel);
                textCancel.Click+=delegate { bottomSheetDiaolog.Dismiss();};
                TextView textSend = view.FindViewById<TextView>(Resource.Id.text_send_comments);
                textSend.Click+=async delegate 
                {
                    string content = textComments.Text;
                    if (string.IsNullOrWhiteSpace(content))
                    {
                        Msg.AppMsg.MakeText(this, "请输入评论内容", Msg.AppMsg.STYLE_INFO).Show();
                        return;
                    }
                    Dialog  waitDialog = CommonHelper.CreateLoadingDialog(this, "正在发送评论数据,请稍后...");
                    try
                    {
                        waitDialog.Show();
                        if (await BlogService.AddArticleComments(CommonHelper.token, article.BlogApp, article.Id, content))
                        {
                            Msg.AppMsg.MakeText(this, GetString(Resource.String.publish_comments_success), Msg.AppMsg.STYLE_INFO).Show();
                            bottomSheetDiaolog.Dismiss();
                        }
                        else
                            Msg.AppMsg.MakeText(this, GetString(Resource.String.publish_comments_fail), Msg.AppMsg.STYLE_INFO).Show();
                    }
                    catch (Exception ex)
                    {
                        Msg.AppMsg.MakeText(this, GetString(Resource.String.publish_comments_fail), Msg.AppMsg.STYLE_INFO).Show();
                        Log.Debug("error:", ex.Message);
                    }
                    finally
                    {
                        waitDialog.Cancel();
                    }
                };
                bottomSheetDiaolog.SetContentView(view);
                bottomSheetDiaolog.Show();
            };
            webView = FindViewById<PullableWebView>(Resource.Id.webview);
            ptrl = FindViewById<PullToRefreshLayout>(Resource.Id.refresh_view);
            ptrl.setOnRefreshListener(this);
            webView.Settings.DefaultTextEncodingName = "utf-8";
            webView.Settings.LoadsImagesAutomatically = true;
            webView.SetWebViewClient(new MyWebViewClient());
            webView.ScrollBarStyle = ScrollbarStyles.InsideOverlay;
            webView.Settings.JavaScriptEnabled = false;
            webView.Settings.SetSupportZoom(false);
            webView.Settings.BuiltInZoomControls = false;
            webView.Settings.CacheMode = CacheModes.CacheElseNetwork;
            webView.Settings.SetLayoutAlgorithm(WebSettings.LayoutAlgorithm.SingleColumn);
            //webView.Settings.UseWideViewPort = true;//设置此属性,可任意比例缩放
            btnViewComments = FindViewById<Button>(Resource.Id.footbar_comments);    
            btnViewComments.Click+=delegate 
            {
                Intent intent = new Intent(this, typeof(ArticleCommentsActivity));
                intent.PutExtra("current", JsonConvert.SerializeObject(article));
                StartActivity(intent); 
            };
            CommonHelper.InitalShare(this, null, true, article.Author, article.Title, article.Avatar, article.Url);
            CommonHelper.InitalBookMark(this, article.Url, article.Title);
            await ptrl.AutoRefresh();
        }


        public void onRefresh(PullToRefreshLayout pullToRefreshLayout)
        {
            BaseService.ExeRequest(async () => 
            {
                content = await BlogService.GetArticleContent(CommonHelper.token, article.Id);
                content = content.Replace("\\r\\n", "</br>").Replace("\\n","</br>").Replace("\\t", "&nbsp;&nbsp;&nbsp;&nbsp;").Replace("\\", string.Empty);
                content = content.Substring(1, content.Length - 2);
                using (var stream = Assets.Open("articlecontent.html"))
                {
                    StreamReader sr = new StreamReader(stream);
                    string html = sr.ReadToEnd();
                    sr.Close();
                    sr.Dispose();
                    html = html.Replace("#title#", article.Title)
                               .Replace("#author#", article.Author)
                           .Replace("#time#", article.PostDate.ToShortDateString())
                               .Replace("#content#", content);
                    webView.LoadDataWithBaseURL("file:///android_asset/", html, "text/html", "utf-8", null);
                }
                pullToRefreshLayout.refreshFinish(0);
            },this);
        }

        public void onLoadMore(PullToRefreshLayout pullToRefreshLayout)
        {
            
        }

        public bool CanLoadMore()
        {
            return false;
        }
    }


    public class MyWebViewClient : WebViewClient
    { 
        public override void OnPageFinished(WebView view, string url)
        {
            if (!view.Settings.LoadsImagesAutomatically)
            {
                view.Settings.LoadsImagesAutomatically = true;
            }
        }

        public override bool ShouldOverrideUrlLoading(WebView view, IWebResourceRequest request)
        {
            return false;
        }

    }

}

实际中,还遇到点儿坑,返回的内容里面包含了一大堆的\r\n ,\\,\t 等一些转义符,还得把\r\n替换成换行符</br>,\t转换成四个空格。

content = content.Replace("\\r\\n", "</br>").Replace("\\n","</br>").Replace("\\t", "&nbsp;&nbsp;&nbsp;&nbsp;").Replace("\\", string.Empty);

目前样式的话,勉强还能看,只能慢慢优化了,毕竟前端美化有点儿弱。

1]W0FID4XRVJRC8X]{%%T66

一键分享

CNBlog分享用的是Mob ShareSDK,官网地址:http://www.mob.com。

本来还很头痛的,一个群友分享了一个他已经绑定好的例子。地址:https://github.com/wtffly/DroidBinding_ShareSDK

image_thumb[5]

用法也很简单,目前的话只能QQ,以及QQ空间分享。分享到每个平台都需要去申请每个平台的开发者权限…0GTRA()HX04{VE8K3HE1Y8B_thumb

对于我们个人开发者来说,太麻烦了。目前CNBlog里头的分享Key是官方Demo里头的,暂时只能支持QQ,以及QQ空间分享,只能等着慢慢去申请,微信的已经快一周了,都没反应,估计没戏。98R8YUCR69GUO(YF0TV)2D4_thumb

 

最后

如果从GitHub Clone下来编译不过的话,一般都是你被墙了,因为VS需要还原这些包,要从谷歌下载这些开发包

image_thumb[7]

这里提供我自己收集的一些开发包:http://pan.baidu.com/s/1jHEkh50

然后下载解压后放到C:\Users\__\AppData\Local\Xamarin\zips目录下就行了。

上篇文章提供了一个APK,有个园友说不能安装,应该是我的CPU架构支持的不够,特重新编译了一个APK,下载地址:

http://pan.baidu.com/s/1eRXVcZ4

image

转载请注明出处 IT胡小帅: http://www.cnblogs.com/CallMeUncle/p/6186006.html

posted @ 2016-12-16 10:52  IT胡小帅  阅读(4118)  评论(35编辑  收藏  举报