• 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=/lang(?:uage)?-(w+)/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:/<!--[wW]*?-->/,prolog:/<?[wW]+??>/,doctype:/<!DOCTYPE[wW]+?>/,cdata:/<![CDATA[[wW]*?]]>/i,tag:{pattern:/</?(?!d)[^s>/=$<]+(?:s+[^s>/=]+(?:=(?:("|')(?:\1|\?(?!1)[wW])*1|[^s'">=]+))?)*s*/?>/i,inside:{tag:{pattern:/^</?[^s>/]+/i,inside:{punctuation:/^</?/,namespace:/^[^s>/:]+:/}},"attr-value":{pattern:/=(?:('|")[wW]*?(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://*[wW]*?*//,atrule:{pattern:/@[w-]+?.*?(;|(?=s*{))/i,inside:{rule:/@[w-]+/}},url:/url((?:(["'])(\(?:
    |[wW])|(?!1)[^\
    ])*1|.*?))/i,selector:/[^{}s][^{};]*?(?=s*{)/,string:{pattern:/("|')(\(?:
    |[wW])|(?!1)[^\
    ])*1/,greedy:!0},property:/(|B)[w-]+(?=s*:)/i,important:/B!important/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[wW]*?>)[wW]*?(?=</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:/(^|[^\])/*[wW]*?*//,lookbehind:!0},{pattern:/(^|[^\:])//.*/,lookbehind:!0}],string:{pattern:/(["'])(\(?:
    |[sS])|(?!1)[^\
    ])*1/,greedy:!0},"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)s+)|(?:catchs+())[a-z0-9_.\]+/i,lookbehind:!0,inside:{punctuation:/(.|\)/}},keyword:/(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)/,"boolean":/(true|false)/,"function":/[a-z0-9_]+(?=()/i,number:/-?(?:0x[da-f]+|d*.?d+(?:e[+-]?d+)?)/i,operator:/--?|++?|!=?=?|<=?|>=?|==?=?|&&?|||?|?|*|/|~|^|%/,punctuation:/[{}[];(),.:]/};
    Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/(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)/,number:/-?(0x[dA-Fa-f]+|0b[01]+|0o[0-7]+|d*.?d+([Ee][+-]?d+)?|NaN|Infinity)/,"function":/[_$a-zA-ZxA0-uFFFF][_$a-zA-Z0-9xA0-uFFFF]*(?=()/i,operator:/--?|++?|!=?=?|<=?|>=?|==?=?|&&?|||?|?|**?|/|~|^|%|.{3}/}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])/(?!/)([.+?]|\.|[^/\
    ])+/[gimyu]{0,5}(?=s*($|[
    ,.;})]))/,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:/[sS]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<script[wW]*?>)[wW]*?(?=</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;
            }
    
        }
    
    }

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

    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\__AppDataLocalXamarinzips目录下就行了。

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

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

    image

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

  • 相关阅读:
    Cocos2d-x开发实例:使用Lambda 表达式
    Cocos2d-x实例:单点触摸事件
    Cocos2d-x中触摸事件
    Cocos2d-x开发实例介绍帧动画使用
    Cocos2d-x开发实例介绍特效演示
    c++ 类型转换
    boost的编译
    c/c++二级指针动态开辟内存
    基于RANSAC的点云面分割算法
    点到平面直线的距离和空间直线的距离
  • 原文地址:https://www.cnblogs.com/CallMeUncle/p/6186006.html
Copyright © 2020-2023  润新知