接下來接著繼續解決ListView中之item加入表情符的顯示能力。回顧之前所建立之資料類別模型中,Site類內有一個Map物件用於保存表情資訊,鍵是表情的名稱,值是表情對應的drawable資源的ID的字串表示之。同時,對照新浪微博的資料可以發現,每一個表情實際上就是對應的一個字串,例如在微博網頁正文中如果遇到[愛你]字樣,就用一個眼冒紅心垂涎三尺的小圖示展現出來,就形成了所謂的表情符。
思路:前述中已能夠為TextView啟動超連結的顯示,讓其解析HTML標籤成功完成了特殊字元的處理,如此作法,是否可為其加入HTML<Image>標籤讓其顯示出圖片呢?經過初步的嘗試,在這裡會遇到兩個問題:一是圖片畫不出來,二是資料中的圖片資源ID無法轉換成圖片資源
解決第一道問題的方法是使用重載的public static SpannedfromHtml(String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler)方法,其中的第二個參數是一個圖片獲取介面,其方法:
public abstract Drawable getDrawable(String source)
用於從一個字串中返回一個Drawable物件。如果我們把Map中的值傳入這個介面,然後想辦法將其轉換成資源ID,再載入進來成Drawable就可以了。
解決第二道問題其實很簡單,Resources類提供了一個方法
public int getIdentifier(String name, String defType, String defPackage)
用於從一個字串獲取資源ID
所有問題均找出解決方法了,修正BlogTextView之代碼如下: 
package com.wenbin.test;

import java.util.Map;
import java.util.Set;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.Html;
import android.text.Html.ImageGetter;
import android.text.util.Linkify;
import android.util.AttributeSet;
import android.widget.TextView;

public class BlogTextView extends TextView {
    private static final int NAMELENGTH=15; //假设昵称不超过15个字符
    private Map<String,String> faceMap;
    private CharSequence text;
   
    private ImageGetter imageGetter = new Html.ImageGetter() {
        @Override
        public Drawable getDrawable(String source) {
              Drawable drawable = null;
              String sourceName=getContext().getPackageName()+":drawable/"+source;
              int id=getResources().getIdentifier(sourceName,null,null);
              if (id!=0){
                  drawable=getResources().getDrawable(id);
                  if (drawable!=null){
                    drawable.setBounds(0, 0,
                                 drawable.getIntrinsicWidth(),
                                 drawable.getIntrinsicHeight());
                  }
              }
              return drawable;
        }
    };

    public BlogTextView(Context context) {
          super(context);
          setAutoLinkMask(Linkify.ALL);
    }

    public BlogTextView(Context context, AttributeSet attrs) {
          super(context, attrs);
          setAutoLinkMask(Linkify.ALL);
    }

    public BlogTextView(Context context, AttributeSet attrs, int defStyle) {
          super(context, attrs, defStyle);
          setAutoLinkMask(Linkify.ALL);
    }
   
    public void setText(CharSequence text,Map<String,String> faceMap){
          this.faceMap=faceMap;
          setText(text);
    }

    @Override
    public CharSequence getText() {
          return text==null?"":text;
    }

    @Override
    public void setText(CharSequence text, BufferType type) {
          this.text=text;
         
          String cs=text.toString();
          String font1="<font color=#339966>";
          String font2="</font>";
         
          //找以'@'开头以':'' '结尾的子串,将其使用font标记进行修饰
          int start=0;
          while(true){
                 start=cs.indexOf('@',start);
                 if (start<cs.length() && start>=0){
                       int end=cs.indexOf(' ',start);
                       if (end<cs.length() && end>0 && end-start<=NAMELENGTH){
                              CharSequence subcs=new String(cs.subSequence(start, end).toString());
                              cs=cs.replace(subcs,font1+subcs+font2 );
                              start+=font1.length()+subcs.length()+font2.length();
                       }
                       else{
                              end=cs.indexOf(':',start);
                              if (end<cs.length() && end>0 && end-start<=NAMELENGTH){
                                    CharSequence subcs=new String(cs.subSequence(start, end).toString());
                                    cs=cs.replace(subcs,font1+subcs+font2 );
                                    start+=font1.length()+subcs.length()+font2.length();
                              }
                       }
                       start+=1;
                 }
                 else{
                       break;
                 }
          }
         
          if (faceMap!=null){
                 //对表情符以img标记进行修饰,改用drawable显示出来
                 Set<String> keys=faceMap.keySet();
                 for(String key:keys){
                       if (cs.contains(key)){
                              cs=cs.replace(key, "<img src=""+faceMap.get(key)+"" mce_src=""+faceMap.get(key)+"">");
                       }
                 }
          }
   
          super.setText(Html.fromHtml(cs,imageGetter,null), type);
    }
}
過程中通過提供一個重載的setText()方法用於將表情的Map傳遞進來。接著再修改BlogAdapter類的updateBlogView()updateRetweeteBlogView()方法,用於傳遞表情資料代碼片斷如下: 
       private void updateBlogView(View view, Blog blog) {
                 TextView userName=(TextView)view.findViewById(R.id.userName);
                 BlogTextView blogText=(BlogTextView)view.findViewById(R.id.blogText);
                 WebView profileImage=(WebView)view.findViewById(R.id.profileImage);
                 ImageView vImage=(ImageView)view.findViewById(R.id.vImage);
                 WebView smallImage=(WebView)view.findViewById(R.id.smallImage);
                 TextView sourceText=(TextView)view.findViewById(R.id.sourceText);

                 userName.setText(blog.getUser().getScreenName());
                 sourceText.setText(context.getString(R.string.from)+blog.getSource());
                           
                 blogText.setText(blog.getText(),blog.getSite().getFaceMap());
                 profileImage.loadUrl(blog.getUser().getProfileImageUrl());
                
                 if (!blog.getUser().isVerified())
                            vImage.setVisibility(View.INVISIBLE);
                 if (blog.getSmallPic().length()>0){
                            smallImage.loadUrl(blog.getSmallPic());
                 }
                 else{
                            smallImage.setVisibility(View.GONE);
                 }
       }

       private void updateRetweeteBlogView(View view, Blog blog) {
                 BlogTextView reBlogText=(BlogTextView)view.findViewById(R.id.reBlogText);
                 WebView reImage=(WebView)view.findViewById(R.id.reImage);
                 String at = "@";
                 String colon=": ";
                 if (blog.getInReplyBlogText().length()>0){
                            if (blog.getInReplyUserScreenName().length()>0){

                                       reBlogText.setText(at+blog.getInReplyUserScreenName()+colon+blog.getInReplyBlogText());
                            }
                            else{
                                       reBlogText.setText(blog.getInReplyBlogText(),blog.getSite().getFaceMap());
                            }
                 }
                 else{
                            reBlogText.setVisibility(View.GONE);
                 }
                
                 if (blog.getRetweetedBlog().getSmallPic().length()>0){
                            reImage.loadUrl(blog.getRetweetedBlog().getSmallPic());
                 }
                 else{
                            reImage.setVisibility(View.GONE);
                 }
       }
再運行程式顯示結果看看,效果如下圖所示:
完成後接著最後來處理一下微博中的圖片部分。在前文中利用WebView來處理URL中的圖片顯示,基本上能達到目標,但是還是有一兩處細節需要處理,以提升使用者體驗。其思路如下:
一是在下載圖片的過程中需要顯示一個背景圖,讓用戶先知道那裡會出現圖片,而不是一片空白。
二是圖片下來以後,要根據圖片的大小動態調整顯示效果,不能出現WebView太大而圖片太小時的白邊。 
解決方法可從WebView繼承一個PictureWebView專門解決上面這兩個問題。首先,要為這個PictureWebView實現WebView.PictureListener介面,該介面提供的public abstract void onNewPicture(WebView view, Picture picture)方法用於在圖片改變時發出通知。其代碼部份如下所示,其中之picture為一個Picture類型的成員變數。 
   @Override
   public void onNewPicture(WebView view, Picture picture) {
          if (picture!=null){
                this.picture=picture;
                DisplayMetrics dm=getContext().getResources().getDisplayMetrics();
                int width=(int) (picture.getWidth()*dm.density);
                int height=(int) (picture.getHeight()*dm.density);
                setPictureListener(null);
                ViewGroup.LayoutParams lp=getLayoutParams();
                lp.width=width;
                lp.height=height;
                setLayoutParams(lp);
          }   
   }  
注意到這裡已經根據picture大小調整了控制項的佈局。因為這個PictureWebView的每個實例針對的只是一條微博中的圖片URL,因此這裡不關注WebView中其他的可顯示元素。 
接下來,為了更好的處理第二道問題,我們改寫一下onDraw()方法,進行背景和圖片的繪製,其代碼如下: 
    @Override
    protected void onDraw(Canvas canvas) {
          super.onDraw(canvas);
          if (picture==null){
                 Drawable background=this.getBackground();
                 if (background!=null){
                    background.setBounds(0,0,background.getIntrinsicWidth(),background.getIntrinsicHeight());
                       background.draw(canvas);
                 }
          }
    } 
這樣,一個自訂定制的WebView就實現了,再修改blogview.xml佈局檔中的WebView控制項為PictureWebView即可,修正後之代碼如下:
<com.wenbin.test.PictureWebView android:id="@+id/profileImage"  
    android:layout_width="48dp"  
    android:layout_height="48dp"  
    android:scrollbars="none"  
    android:layout_alignParentLeft="true"  
    android:background="@drawable/portrait">  
</com.wenbin.test.PictureWebView>  
運行一下程式,效果圖示如下,此時無論是頭像還是微博中的圖片,在未下載完時是顯示背景圖的,下載完後會自動調整佈局進行適應,而且不存在無用的邊框。

結語:在代碼中沒有注重演算法之類的事情,也沒有達到商用的程度,主要還是通過模仿新浪微博的用戶端介面來靈活應用一下Android的控制項,同時也可以看到Android框架帶給開發人員的靈活程度。 
<<完成>>

0 意見: