內容摘要
該控件能夠應用於內容資訊展示的功能模塊中,如:騰訊和新浪微博的微博列表,微信朋友圈及其它社交類應用的好友動態展示列表等;實現了類似騰訊微博的微博列表展示功能,包含微博文本內容,表情,圖片,話題和用戶可點超鏈接等(請參見如下效果圖)。該功能在實際項目開發中非常常見,除微博應用外,微信的朋友圈,陌陌、QQ空間的好友動態等也都有類似功能
- RecyclerView使用和嵌套問題
- 動態設置圖片網格寬高
- 正則表達式的使用
- Linkify實現自定義超鏈接
- TextView富文本顯示
- 點贊動畫漸變動畫效果
效果圖
列表的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來接收意圖中的參數。
當點擊超鏈接的時候,會調起/啓動一個與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);
}