Xamarin Android 打造屬於本身的博客園APP(4)

前言

今天早上終於把本地的Git環境搞好,而後順利的丟到了Github上,主要是我如今用的電腦以前是其它同事用的 ,是他的Git帳號,切換成我本身的用了太長時間了,也主要是之前沒有咋用過Git命令。javascript

先說地址吧:https://github.com/HuUncle/Xamarin-Android-CNBlog,以爲還能夠的朋友能夠點個星,畢竟我是個小菜鳥,寫個APP不容易啊。css

大概說下項目結構吧,總體分爲兩層,一個XamarinAndroid工程,一個可移植項目庫,裏面封裝的博客園的API請求,打算在寫IOS客戶端的時候移植過去。html

image_thumb5

接口請求的話,全是用的異步在請求,畢竟是APP,總不能UI卡死吧。前端

CNBlog功能描述:

已實現功能:java

1.博客園帳號登陸node

2.分頁獲取首頁文章,分頁精華文章,分頁知識庫文章,分頁新聞android

3.獲取文章、新聞評論列表,發送評論git

4.文章、新聞在線收藏、分享github

5.查看個人博客,查看個人收藏web

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

相關文章
相關標籤/搜索