微信朋友圈,QQ空間,微博等列表展示的功能實現

內容摘要

該控件能夠應用於內容資訊展示的功能模塊中,如:騰訊和新浪微博的微博列表,微信朋友圈及其它社交類應用的好友動態展示列表等;實現了類似騰訊微博的微博列表展示功能,包含微博文本內容,表情,圖片,話題和用戶可點超鏈接等(請參見如下效果圖)。該功能在實際項目開發中非常常見,除微博應用外,微信的朋友圈,陌陌、QQ空間的好友動態等也都有類似功能

  1. RecyclerView使用和嵌套問題
  2. 動態設置圖片網格寬高
  3. 正則表達式的使用
  4. Linkify實現自定義超鏈接
  5. TextView富文本顯示
  6. 點贊動畫漸變動畫效果

效果圖

朋友圈列表的實現

列表的item佈局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="5dp"
    android:background="@color/white"
    android:descendantFocusability="blocksDescendants"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_marginTop="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp">

        <ImageView
            android:id="@+id/iv_avatar"
            android:layout_width="45dp"
            android:layout_height="45dp"
            android:layout_centerVertical="true"
            android:background="#11000000"
            android:scaleType="centerCrop"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="10dp"
            android:layout_toRightOf="@id/iv_avatar"
            android:orientation="vertical">

            <TextView
                android:id="@+id/tv_user"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="120dp"
                android:text="用戶名"
                android:textColor="@color/black"
                android:textSize="16sp"/>

            <TextView
                android:id="@+id/tv_user_introduction"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="5dp"
                android:singleLine="true"
                android:text="用戶相關介紹"
                android:textColor="@color/item_text_secondary"
                android:textSize="14sp"/>
        </LinearLayout>

        <TextView
            android:id="@+id/tv_date"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_marginTop="5dp"
            android:text="0000-00-00"
            android:textColor="@color/item_text_secondary"
            android:textSize="14sp"/>

    </RelativeLayout>

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="5dp"
        android:text="這是微博內容...這是微博內容..."
        android:textColor="@color/item_text_main"
        android:textSize="16sp"/>

    <!--顯示微博圖片-->
    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_weibo_images"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:listSelector="@color/transparent"
        android:visibility="gone"/>

    <View
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="@color/activity_bg"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:orientation="horizontal">

        <FrameLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:clickable="true">

            <TextView
                android:id="@+id/tv_forward"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:drawableLeft="@drawable/selector_btn_share"
                android:textColor="@color/item_text_secondary"
                android:drawablePadding="5dp"
                android:gravity="center"
                android:text="0"/>

        </FrameLayout>

        <FrameLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:clickable="true">

            <TextView
                android:id="@+id/tv_comment"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:drawableLeft="@drawable/selector_btn_comment"
                android:textColor="@color/item_text_secondary"
                android:drawablePadding="5dp"
                android:gravity="center"
                android:text="0"/>

        </FrameLayout>

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:gravity="center"
            android:clickable="true">

            <CheckBox
                android:id="@+id/cb_like"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:layout_gravity="center"
                android:button="@color/transparent"
                android:checked="false"
                android:drawableLeft="@drawable/selector_btn_prize"
                android:drawablePadding="5dp"
                android:background="@color/transparent"
                android:textColor="@color/item_text_secondary"
                android:gravity="center"/>

            <TextView
                android:id="@+id/tv_like"
                android:text="0"
                android:textColor="@color/item_text_secondary"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

        </LinearLayout>

    </LinearLayout>

</LinearLayout>

item中的RecyclerView用於顯示0-9張圖片,根據服務器返回的圖片數量不同顯示的行列數和圖片的大小也不同,需要動態設置,由於這個列表是RecyclerView,item中也使用了RecyclerView,這就產生了RecyclerView的嵌套

id爲tv_content的TextView用於顯示內容,內容中包含了表情圖片和超鏈接

json數據格式

{
    "result":true,
    "weibo":[
        {
            "avatar":"avatar_01",
            "comment":5,
            "content":"我裝作看不懂的樣子[呲牙][偷笑][偷笑],單身狗保重 [再見][再見] @冷笑話精選",
            "date":1489223423501,
            "forward":8,
            "imageUrls":[
                "pic_1",
                "pic_2",
                "pic_3",
                "pic_4",
                "pic_5",
                "pic_6",
                "pic_7",
                "pic_8",
                "pic_9"
            ],
            "like":10,
            "user_introduction":"最冷笑話精選,每天分享笑話N枚,你的貼身開心果。",
            "username":"冷笑話精選"
        }
     ]
}

對應的實體類

public class WeChat {

    public boolean result;
    public List<WeiboEntity> weibo;

    public static class WeiboEntity {
        public String       avatar;
        public int          comment;
        public String       content;
        public long         date;
        public int          forward;
        public int          like;
        public String       user_introduction;
        public String       username;
        public List<String> imageUrls;
    }
}

動態設置圖片宮格數

根據圖片的數量,動態設置RecyclerView的列數和寬度

  • 如果圖片數量爲0,則隱藏RecyclerView
  • 如果圖片數量爲1,RecyclerView列數設爲1列,寬度設爲WRAP_CONTENT
  • 如果圖片數量爲4,RecyclerView列數設爲2列,寬度設爲兩個圖片宮格的寬度
  • 其它,RecyclerView列數設爲3列,寬度設爲MATCH_PARENT
// 刷新item佈局中子控件的顯示
    @Override
    protected void onRefreshView(WeChat.WeiboBean bean, int position) {
        // 顯示用戶名
        tvUser.setText(bean.getUsername());

        // 顯示用戶介紹
        if (TextUtils.isEmpty(bean.getUser_introduction())) {
            tvUserIntroduction.setVisibility(View.GONE);
        } else {
            tvUserIntroduction.setVisibility(View.VISIBLE);
            tvUserIntroduction.setText(bean.getUser_introduction());
        }
        // 顯示頭像
        int imageResId = Global.getResId(context, bean.getAvatar());
        ivAvatar.setBackgroundResource(imageResId);

        // 微博內容
        // tvContent.setText(bean.getContent());
        EmojiUtil.setText(tvContent, bean.getContent());
        LinkifyUtil.addCustomLink(tvContent);
        LinkifyUtil.addCustomLink2(tvContent);

        // 發表時間
        tvDate.setText(Global.formatDate(bean.getDate()));

        // 顯示微博圖片
        int imageCount = bean.getImageUrls() == null
                ? 0 : bean.getImageUrls().size();
        if (imageCount == 0) {      // 沒有微博圖片
            rvWeiboImages.setVisibility(View.GONE);
        } else {    // 有微博圖片
            rvWeiboImages.setVisibility(View.VISIBLE);
            imageAdapter.setDatas(bean.getImageUrls()); // 刷新圖片顯示

            // 動態的指定圖片宮格的寬高和RecyclerView的寬度
            // 1張圖片 -> 1列
            // 4張圖片 -> 2列
            // 其它    -> 3列
            ViewGroup.LayoutParams param = rvWeiboImages.getLayoutParams();
            if (imageCount == 1) {
                layoutManager.setSpanCount(1);
                param.width = ViewGroup.LayoutParams.WRAP_CONTENT;
            } else if (imageCount == 4) {
                layoutManager.setSpanCount(2);
                // 兩個圖片宮格的寬度
                param.width = Global.getGridWidth() * 2;
            } else {        // 3列
                layoutManager.setSpanCount(3);
                param.width = ViewGroup.LayoutParams.MATCH_PARENT;
            }
        }
    }

動態設置圖片的大小

  • 1張圖片,宮格的寬高爲圖片的寬高
  • 其它情況,宮格的寬高爲屏幕寬度的三分之一
    // 刷新item子控件的顯示
    @Override
    protected void onRefreshView(String imagePath, int position) {
        // 動態設置圖片宮格的寬高
        // 1張圖片  ->   宮格的寬高爲圖片的寬高
        // 其它情況  ->  宮格的寬高爲Global.getGridWidth()
        ViewGroup.LayoutParams param = super.itemView.getLayoutParams();
        if (super.adapter.getItemCount() == 1) {    // 一張圖片
            // 圖片資源id
            int imageResId = Global.getResId(context, imagePath);
            Bitmap bitmap = BitmapFactory.decodeResource(
                    context.getResources(), imageResId);
            // 指定宮格的寬高爲圖片的寬高
            param.width = bitmap.getWidth();
            param.height = bitmap.getHeight();
            // 顯示圖片
            ivImage.setBackgroundResource(imageResId);
        } else {    // 多張圖片
            // 顯示宮格圖片
            int imageResId = Global.getResId(context, imagePath);
            ivImage.setBackgroundResource(imageResId);

            param.width = Global.getGridWidth();    // 指定宮格圖片的寬
            param.height = Global.getGridWidth();
        }
    }

TextView富文本顯示

顯示文本中的表情,把文本中如[呲牙][偷笑][偷笑]的文字替換成表情圖片,實現TextView的富文本顯示(圖文混排)。需要用正則去匹配文本中是否包含表情,匹配成功,表示文本中包含表情,用ImageSpan封裝表情圖片,再ImageSpan將設置給SpannableString,把文本中的表示表情的文字替換掉,最後將SpannableString設置給TextView即可。

正則參考:

[高興]    \\[([A-Za-z\u4E00-\u9FA5]+)\\]
@用戶     \\@([A-Za-z0-9\u4E00-\u9FA5]+)
#話題#    \\#([A-Za-z0-9\u4E00-\u9FA5]+)\\#
public class EmojiUtil {

    /** 顯示文本和表情 */
    public static void setText(TextView textView, String text) {
        Context context = textView.getContext();
        Resources resources = context.getResources();
        SpannableString ss = new SpannableString(text);

        // 正則表達式: [高興]
        Pattern p = Pattern.compile("\\[([A-Za-z\u4E00-\u9FA5]+)\\]");
        Matcher matcher = p.matcher(ss);
        while (matcher.find()) {
            // 匹配到一個表情字符串
            String emoji = matcher.group();
            // 過濾非表情符,比如: [xxx]
            if (EMOJI_DATAS.containsKey(emoji)) {   // 是表情才處理
                // System.out.println("----------" + emoji);
                // 指定了一張圖片
                Bitmap bitmap = BitmapFactory.decodeResource(resources, EMOJI_DATAS.get(emoji));
                bitmap = Global.createBitmap(bitmap, Global.dp2px(20));     // 圖片的寬高爲20dp
                ImageSpan span = new ImageSpan(context, bitmap, ImageSpan.ALIGN_BOTTOM);
                int start = matcher.start();
                int end = matcher.end();
                ss.setSpan(span, start, end, 0);
            }
        }
        textView.setText(ss);
    }

    private static final HashMap<String, Integer> EMOJI_DATAS = new HashMap<String, Integer>();

    static {
        EMOJI_DATAS.put("[微笑]", R.drawable.smiley_0);
        ...
    }
}

讓文字顯示顏色

/**
     * 讓某幾個文字顯示顏色
     * @param string
     * @param color
     * @return
     */
    private CharSequence showTextWithColor(String string,int color) {
        SpannableString ss = new SpannableString(string);
        // BackgroundColorSpan 背景色
        ForegroundColorSpan colorSpan = new ForegroundColorSpan(color);
        int end = string.indexOf("等");
        ss.setSpan(colorSpan, 0, end, SpannableString.SPAN_INCLUSIVE_EXCLUSIVE);
        return ss;
    }

讓圖片和文字一起顯示

/**
     * 讓圖片和文字一起顯示
     * @param text
     * @param imageRes
     * @return
     */
    private SpannableString showTextWithImage(String text,int imageRes){
        SpannableString ss = new SpannableString(text);
        Drawable drawable = getResources().getDrawable(imageRes);

        //設置邊界
//      drawable.setBounds(0,0,drawable.getIntrinsicWidth(),drawable.getIntrinsicHeight());
        drawable.setBounds(0,0,20,20);
        ImageSpan span = new ImageSpan(drawable);

        int start = text.indexOf("[");
        int end = text.indexOf("]")+1;
        ss.setSpan(span, start,end,SpannableString.SPAN_INCLUSIVE_EXCLUSIVE);

        return ss;
    }

設置超鏈接

// 讓某段文字可以被點擊並跳轉超鏈接
String text = "詳情請點擊<a href='http://www.baidu.com'>百度</a>";
Spanned spanned = Html.fromHtml(text);
text3.setText(spanned);
text3.setMovementMethod(LinkMovementMethod.getInstance());//設置可以點擊超鏈接

讓某段文字可以被點擊並自定義點擊的邏輯操作

// 讓某段文字可以被點擊並自定義點擊的邏輯操作
String string = "王二,小明,大兵等覺得很贊";
SpannableString ss= new SpannableString(string);
MyUrlSpan urlSpan= new MyUrlSpan(string.substring(0, string.indexOf(",")));
ss.setSpan(urlSpan, 0, 2, SpannableString.SPAN_INCLUSIVE_EXCLUSIVE);
text4.setText(ss);
text4.setMovementMethod(LinkMovementMethod.getInstance());
class MyUrlSpan extends URLSpan{
        public MyUrlSpan(String url) {
            super(url);
        }

        @Override
        public void onClick(View widget) {
            // 自定義點擊的操作邏輯,默認實現是獲取url,打開瀏覽器
            Toast.makeText(MainActivity.this, getURL(), 0).show();
            widget.clearFocus();
        }

        @Override
        public void updateDrawState(TextPaint ds) {
            super.updateDrawState(ds);
            ds.setColor(Color.RED); // 設置文字顏色
            ds.setUnderlineText(false); // 設置是否顯示下劃線
        }
}

自定義超鏈接

關於TextView 網頁,電話,郵箱的自動識別。設置android:autoLink=”email|web|phone|map”屬性後,TextView 可自動識別電話、郵箱、網址、地圖爲超鏈接。

<TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scrollbarStyle="insideOverlay"
        android:scrollbars="vertical"
        android:autoLink="email|web|phone|map"
        android:text=" 電話:13609000000,郵箱:[email protected],網址:http://www.google.com " />

添加自定義超鏈接,把內容中如@冷笑話精選#編程##講故事#的文本顯示爲超鏈接,高亮顯示並支持點擊。先使用Linkify.MatchFilter 匹配過濾器過濾內容中的超鏈接,TextView在顯示的內容要識別鏈接時,調用Linkify.addLinks()

public class LinkifyUtil {

    /**
     * 添加自定義超鏈接
     */
    public static void addCustomLink(TextView textView) {
        // @用戶:
        Pattern pattern = Pattern.compile("\\@([A-Za-z0-9\u4E00-\u9FA5]+)\\.?");
        // http://www.qq.com/path?uid=1&username=xx
        String scheme = "weibo://user?uid=";

        // 匹配過濾器
        Linkify.MatchFilter matchFilter = new Linkify.MatchFilter() {
            @Override
            public boolean acceptMatch(CharSequence s, int start, int end) {
                String text = s.subSequence(start, end).toString();
                // System.out.println("----text: " + text);
                if (text.endsWith(".")) { // 郵箱,不需要匹配
                    return false;
                } else {
                    return true;    // 返回true會顯示爲超鏈接
                }
            }
        };
        Linkify.TransformFilter transformFilter = null;
        Linkify.addLinks(textView, pattern, scheme, matchFilter, transformFilter);
    }

    public static void addCustomLink2(TextView textView) {
        // @用戶:
        Pattern pattern = Pattern.compile("\\#([A-Za-z0-9\u4E00-\u9FA5]+)\\#");
        // http://www.qq.com/path?uid=1&username=xx
        String scheme = "weibo://topic?uid=";
        // 匹配過濾器
        Linkify.MatchFilter matchFilter = new Linkify.MatchFilter() {
            @Override
            public boolean acceptMatch(CharSequence s, int start, int end) {
                String text = s.subSequence(start, end).toString();
                System.out.println("----text: " + text);
                return true;
            }
        };
        Linkify.TransformFilter transformFilter = new Linkify.TransformFilter() {
            @Override
            public String transformUrl(Matcher match, String url) {
                return match.group(1);
            }
        };
        Linkify.addLinks(textView, pattern, scheme, matchFilter, transformFilter);
    }
}

設置自定義的鏈接後,點擊超鏈接後會出錯。 因爲沒有找到Activity可以處理髮起的Intent, 需要定義兩個Activity來接收意圖中的參數。

ActivityNotFoundException

當點擊超鏈接的時候,會調起/啓動一個與Linkify.addLinks()方法中的scheme對應的Activity

public class TopicActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // weibo://user?uid=@冷笑話精選
        Uri uri = getIntent().getData();
        String topic = uri.getQueryParameter("uid");

        TextView textView = new TextView(this);
        textView.setGravity(Gravity.CENTER);
        textView.setTextColor(Color.RED);
        textView.setText(topic);
        textView.setTextSize(20);

        setContentView(textView);
    }

}
public class UserActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // weibo://user?uid=@冷笑話精選
        Uri uri = getIntent().getData();
        String username = uri.getQueryParameter("uid");

        TextView textView = new TextView(this);
        textView.setGravity(Gravity.CENTER);
        textView.setTextColor(Color.GRAY);
        textView.setText(username);
        textView.setTextSize(20);

        setContentView(textView);
    }

}

在清單文件中配置以上Activity,給Activity設置action、category、data

<!--點擊用戶鏈接時,要調起該Activity-->
<activity android:name=".ui.activity.UserActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:scheme="weibo" android:host="user"/>
    </intent-filter>
</activity>
<activity android:name=".ui.activity.TopicActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:scheme="weibo" android:host="topic"/>
    </intent-filter>
</activity>

點贊動畫

在MainActivity的佈局文件中,有一個TextView,是用來執行點贊後的+1的動畫(向上平移,透明度變小,放大)。 該控件開始時隱藏,執行點贊動畫時,注意不是列表項中的控件執行動畫。

// WeiboHolder.java
cbLike.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if (isChecked) {
            // 獲取當前點擊控件相對於窗口的所在位置
            int[] locations = new int[2];
            tvLike.getLocationInWindow(locations);
            ((MainActivity) context).animateUp(locations);
        }
    }
});
public void animateUp(int[] locations) {
    // 減去狀態欄高度24dp
    int currentY = locations[1] - Global.dp2px(24);
    tvLike.setVisibility(View.VISIBLE);
    tvLike.setTranslationX(locations[0]);
    tvLike.setTranslationY(currentY);
    tvLike.setScaleY(1);
    tvLike.setScaleX(1);
    tvLike.setAlpha(1f);

    // 往上移動30dp
    int top = currentY - Global.dp2px(30);
    tvLike.animate().alpha(0).translationY(top)
            .setInterpolator(new DecelerateInterpolator())
            .scaleX(1.2f).scaleY(1.2f).setDuration(1000);
}

代碼:https://github.com/JackChan1999/WeChatDemo

發佈了273 篇原創文章 · 獲贊 266 · 訪問量 123萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章