Android项目实战(十六):QQ空间实现(一)—— 展示说说中的评论内容并有相应点击事件

简介: 大家都玩QQ空间客户端,对于每一个说说,我们都可以评论,那么,对于某一条评论: 白雪公主 回复 小矮人 : 你们好啊~ 我们来分析一下: 1、QQ空间允许我们 点击 回复人和被回复人的名字就可以进入对于用户的个人主页(即点击文字“白雪公主”/“小矮人”,就可以进入到这俩用户相应个人...

大家都玩QQ空间客户端,对于每一个说说,我们都可以评论,那么,对于某一条评论:

白雪公主 回复 小矮人 : 你们好啊~

我们来分析一下:

1、QQ空间允许我们 点击 回复人和被回复人的名字就可以进入对于用户的个人主页(即点击文字“白雪公主”/“小矮人”,就可以进入到这俩用户相应个人主页)
2、点击 回复的文字,就可以对回复人进行回复(即点击评论中回复的内容“你们好啊~”,便对弹出一个编辑框对回复人“白雪公主”进行回复)
3、回复人 和 被回复人 的名字是有颜色的

效果图:

作为一个android开发者,我们要实现对一个TextView :

1、点击不同的文字部分(文字个数还不确定)有相应的响应操作(进入个人主页等等)

2、一个TextView中某些文字有不同的颜色

下面学习如何实现-->

----------------------------------------------------------------------------------

首先介绍下QQ空间说说列表这一个界面(fragment来实现)的整体框架:

1、使用RecyclerView来展示说说列表   why? 

1、RecyclerView 自带实现复用机制,对于工作1--2年左右的,不建议使用自己写的复用ListView
2、RecyclerView 方便对于某一个item 项的增删改操作 (大优势),比如控件删除该说说的功能的实现 RecyclerView实现更好

2、每一个item 内部 ,评论文字部分 用不可以滑动的ListView(RecyclerView理论上更棒,反正不可以滑动就行了)来展示 (博主一开始想的是用LinearLayout 内部 动态添加TextView来展示,经测试,太麻烦且易出错)

不可滑动的ListView 代码 --> 自定义不可滑动的ListView和GridView

-----------------------------------------------------------------------------------

下面用一个Demo来学习如何实现说说评论的效果:

首先布局文件,就一个不可滑动的ListView,我们Demo只展示评论列表

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
    
  <!-- 注意listview要去除分割线 -->
<com.xqx.com.qqhome.NoScrollListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@null" >
</com.xqx.com.qqhome.NoScrollListView>

</RelativeLayout>

然后是Item项的布局文件(评论文字):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/txt_comment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="2dp"
        />
</LinearLayout>

-----------------------------------------------------------------------------------

看java文件部分:

MainActivity.java

很简单,自己创建了5条评论,添加到自己写的适配器中

注意:评论有的是没有被回复人的!

 

public class MainActivity extends Activity {

    private NoScrollListView noScrollListView;

    /* --------- 数据源----------- */
    //记录回复说说用户的集合
    private ArrayList<String> name;
    //记录被回复说说用户的集合
    private ArrayList<String> toName;
    //记录评论内容的集合
    private ArrayList<String> content;

    /* --------- 适配器------------*/
    private CommentAdapter adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        noScrollListView = (NoScrollListView) findViewById(R.id.listview);

        name = new ArrayList<>();
        toName = new ArrayList<>();
        content = new ArrayList<>();

        //添加数据 ,Demo只添加5条评论
        name.add("白雪公主");
        toName.add("小矮人");
        content.add("你们好啊~");

        name.add("小矮人");
        toName.add("白雪公主");
        content.add("白雪公主,早上好啊~");

        name.add("王子");
        toName.add("");
        content.add("这条说说很有道理的样子啊~");

        name.add("国王");
        toName.add("");
        content.add("我很喜欢这条说说~");

        name.add("白雪公主");
        toName.add("王子");
        content.add("你也是XX的朋友啊?");

        adapter = new CommentAdapter(name,toName,content,this);
        noScrollListView.setAdapter(adapter);

    }

}

 

 

 

-----------------------------------------------------------------------------------

 布局文件有了,MainActivity有了,剩下最主要的适配器了

 看下自定义适配器所需要的属性 和 写个必要方法:

public class CommentAdapter extends BaseAdapter {

    /* --------- 数据源----------- */
    //记录回复说说用户的集合
    private ArrayList<String> name;
    //记录被回复说说用户的集合
    private ArrayList<String> toName;
    //记录评论内容的集合
    private ArrayList<String> content;

    private Context context;

    public CommentAdapter(ArrayList<String> name, ArrayList<String> toName, ArrayList<String> content, Context context) {
        this.name = name;
        this.toName = toName;
        this.content = content;
        this.context = context;
    }

    @Override
    public int getCount() {
        int ret = 0;
        if (name != null&&name.size()!=0)
            ret = name.size();
        return ret;
    }

    @Override
    public Object getItem(int position) {
        return position;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }
  class ViewHolder{
   TextView txt_comment;
  }

 

重点来了 getView() ~~

首先 建议大家要看下这几篇文章

(转) SpannableString与SpannableStringBuilder

TextView显示html样式的文字

浅谈ClickableSpan , 实现TextView文本某一部分文字的点击响应

然后~~

注释都在代码中:

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
        //其实评论一般都是文字,高级点的带有图片评论,光文字的话复用不复用就没什么大区别了
        View view = null;
        if(convertView!=null)
        {
            view = convertView;
        }
        else
        {
            view = LayoutInflater.from(context).inflate(R.layout.item_comment, parent,false);
        }
        ViewHolder holder = (ViewHolder) view.getTag();
        if(holder==null)
        {
            holder = new ViewHolder();
            holder.txt_comment = (TextView) view.findViewById(R.id.txt_comment);

            view.setTag(holder);
        }
        //给相应位置的文字赋内容
        if (name != null && name.size()!=0) {
            StringBuilder actionText = new StringBuilder();

            //谁回复
            actionText.append("<a style=\"text-decoration:none;\" href='name' ><font color='#1468a3'>"
                    + name.get(position)  + "</font> </a>");

            // 回复谁,被回复的人可能不存在。
            if(toName.get(position)!=null&&toName.get(position).length()>0) {
                actionText.append("回复");
                actionText.append("<font color='#1468a3'><a style=\"text-decoration:none;\" href='toName'>"
                         + toName.get(position) + " " + " </a></font>");
            }
            // 内容
            actionText.append("<font color='#484848'><a style=\"text-decoration:none;\" href='content'>"
                    + ":" + content.get(position) + " " + " </a></font>");

            holder.txt_comment.setText(Html.fromHtml(actionText.toString()));
            holder.txt_comment.setMovementMethod(LinkMovementMethod
                    .getInstance());
            CharSequence text = holder.txt_comment.getText();
            int ends = text.length();
            Spannable spannable = (Spannable) holder.txt_comment.getText();
            URLSpan[] urlspan = spannable.getSpans(0, ends, URLSpan.class);
            SpannableStringBuilder stylesBuilder = new SpannableStringBuilder(text);
            stylesBuilder.clearSpans();

            for (URLSpan url : urlspan) {
                FeedTextViewURLSpan myURLSpan = new FeedTextViewURLSpan(url.getURL(),
                        context,name.get(position),toName.get(position),content.get(position));
                stylesBuilder.setSpan(myURLSpan, spannable.getSpanStart(url),
                        spannable.getSpanEnd(url), spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
            holder.txt_comment.setText(stylesBuilder);
            holder.txt_comment.setFocusable(false);
            holder.txt_comment.setClickable(false);
            holder.txt_comment.setLongClickable(false);

        }

        return view;
    }

    static class FeedTextViewURLSpan extends ClickableSpan {
        private String clickString;
        private Context context;
        // 回复人的名字
        private String name;
        // 被回复人的名字
        private String toName;
        // 评论内容
        private String content;

        public FeedTextViewURLSpan(String clickString, Context context, String name, String toName, String content) {
            this.clickString = clickString;
            this.context = context;
            this.name = name;
            this.toName = toName;
            this.content = content;
        }

        @Override
        public void updateDrawState(TextPaint ds) {
            ds.setUnderlineText(false);
            //给标记的部分 的文字 添加颜色
            if(clickString.equals("toName")){
                ds.setColor(context.getResources().getColor(R.color.blue));
            }else if(clickString.equals("name")){
                ds.setColor(context.getResources().getColor(R.color.blue));
            }
        }

        @Override
        public void onClick(View widget) {
            // 根据文字的标记 来进行相应的 响应事件
            if (clickString.equals("toName")) {
                //可以再次进行跳转activity的操作
                Toast.makeText(context,"点击了"+toName,Toast.LENGTH_SHORT).show();
            } else if (clickString.equals("name")) {
                //可以再次进行跳转activity的操作
                Toast.makeText(context,"点击了"+name,Toast.LENGTH_SHORT).show();
            } else if(clickString.equals("content")){
                //可以再次进去回复评论的操作
                Toast.makeText(context,"点击了"+content,Toast.LENGTH_SHORT).show();
            }
        }
    }

 

 适配器完整代码:

import android.content.Context;
import android.text.Html;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextPaint;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.BaseAdapter;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;


public class CommentAdapter extends BaseAdapter {

    /* --------- 数据源----------- */
    //记录回复说说用户的集合
    private ArrayList<String> name;
    //记录被回复说说用户的集合
    private ArrayList<String> toName;
    //记录评论内容的集合
    private ArrayList<String> content;

    private Context context;

    public CommentAdapter(ArrayList<String> name, ArrayList<String> toName, ArrayList<String> content, Context context) {
        this.name = name;
        this.toName = toName;
        this.content = content;
        this.context = context;
    }

    @Override
    public int getCount() {
        int ret = 0;
        if (name != null&&name.size()!=0)
            ret = name.size();
        return ret;
    }

    @Override
    public Object getItem(int position) {
        return position;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
     public View getView(int position, View convertView, ViewGroup parent) {
        //其实评论一般都是文字,高级点的带有图片评论,光文字的话复用不复用就没什么大区别了
        View view = null;
        if(convertView!=null)
        {
            view = convertView;
        }
        else
        {
            view = LayoutInflater.from(context).inflate(R.layout.item_comment, parent,false);
        }
        ViewHolder holder = (ViewHolder) view.getTag();
        if(holder==null)
        {
            holder = new ViewHolder();
            holder.txt_comment = (TextView) view.findViewById(R.id.txt_comment);

            view.setTag(holder);
        }
        //给相应位置的文字赋内容
        if (name != null && name.size()!=0) {
            StringBuilder actionText = new StringBuilder();

            //谁回复
            actionText.append("<a style=\"text-decoration:none;\" href='name' ><font color='#1468a3'>"
                    + name.get(position)  + "</font> </a>");

            // 回复谁,被回复的人可能不存在。
            if(toName.get(position)!=null&&toName.get(position).length()>0) {
                actionText.append("回复");
                actionText.append("<font color='#1468a3'><a style=\"text-decoration:none;\" href='toName'>"
                        + toName.get(position) + " " + " </a></font>");
            }
            // 内容
            actionText.append("<font color='#484848'><a style=\"text-decoration:none;\" href='content'>"
                    + ":" + content.get(position) + " " + " </a></font>");

            holder.txt_comment.setText(Html.fromHtml(actionText.toString()));
            holder.txt_comment.setMovementMethod(LinkMovementMethod
                    .getInstance());
            CharSequence text = holder.txt_comment.getText();
            int ends = text.length();
            Spannable spannable = (Spannable) holder.txt_comment.getText();
            URLSpan[] urlspan = spannable.getSpans(0, ends, URLSpan.class);
            SpannableStringBuilder stylesBuilder = new SpannableStringBuilder(text);
            stylesBuilder.clearSpans();

            for (URLSpan url : urlspan) {
                FeedTextViewURLSpan myURLSpan = new FeedTextViewURLSpan(url.getURL(),
                        context,name.get(position),toName.get(position),content.get(position));
                stylesBuilder.setSpan(myURLSpan, spannable.getSpanStart(url),
                        spannable.getSpanEnd(url), spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
            holder.txt_comment.setText(stylesBuilder);
            holder.txt_comment.setFocusable(false);
            holder.txt_comment.setClickable(false);
            holder.txt_comment.setLongClickable(false);

        }

        return view;
    }

    static class FeedTextViewURLSpan extends ClickableSpan {
        private String clickString;
        private Context context;
        // 回复人的名字
        private String name;
        // 被回复人的名字
        private String toName;
        // 评论内容
        private String content;

        public FeedTextViewURLSpan(String clickString, Context context, String name, String toName, String content) {
            this.clickString = clickString;
            this.context = context;
            this.name = name;
            this.toName = toName;
            this.content = content;
        }

        @Override
        public void updateDrawState(TextPaint ds) {
            ds.setUnderlineText(false);
            //给标记的部分 的文字 添加颜色
            if(clickString.equals("toName")){
                ds.setColor(context.getResources().getColor(R.color.blue));
            }else if(clickString.equals("name")){
                ds.setColor(context.getResources().getColor(R.color.blue));
            }
        }

        @Override
        public void onClick(View widget) {
            // 根据文字的标记 来进行相应的 响应事件
            if (clickString.equals("toName")) {
                //可以再次进行跳转activity的操作
                Toast.makeText(context,"点击了"+toName,Toast.LENGTH_SHORT).show();
            } else if (clickString.equals("name")) {
                //可以再次进行跳转activity的操作
                Toast.makeText(context,"点击了"+name,Toast.LENGTH_SHORT).show();
            } else if(clickString.equals("content")){
                //可以再次进去回复评论的操作
                Toast.makeText(context,"点击了"+content,Toast.LENGTH_SHORT).show();
            }

        }
    }

    class ViewHolder{
        TextView txt_comment;
    }

}
CommentAdapter.java

 

-----------------------------------------------------------------------------------

 如何实现QQ空间说说列表评论的展示介绍完了~~

 那么如何 回复评论呢?

      如何将新评论的评论及时的显示在当前列表呢?

      之后的博客继续讨论~~~

 

相关知识:

QQ空间实现(二)—— 分享功能 / 弹出PopupWindow

 

博主现在从事社交类社区类APP开发,有同领域的朋友欢迎关注交流~~~                                                                                        

         

相关文章
|
Android开发
flutter中实现仿Android端的onResume和onPause方法
flutter中实现仿Android端的onResume和onPause方法
|
9月前
|
Java API Android开发
Android项目实战:使用Retrofit构建天气预报应用
1. 项目简介 在这个Android项目实战中,我们将构建一个简单的天气预报应用。用户可以输入城市名称,获取该城市的实时天气信息、未来几天的天气预报以及其他相关信息。为了实现这个功能,我们将使用Retrofit框架进行网络请求,从OpenWeatherMap API获取天气数据。
158 0
|
11月前
|
调度 Android开发
Android空间架构与自定义控件详解-更新中
Android空间架构与自定义控件详解-更新中
62 0
|
Android开发 容器
Android实现面包屑效果,支持Fragment联动
Android实现面包屑效果,支持Fragment联动
|
Android开发
Android实现连线题效果
Android实现连线题效果
|
Android开发
Android Studio项目中的Gradle视图内容
Android Studio项目中的Gradle视图内容
|
JavaScript API Android开发
Android,iOS打开手机QQ与指定用户聊天界面
Android,iOS打开手机QQ与指定用户聊天界面
175 0
|
Android开发
Android实现调用系统相机录像及实现录音
Android实现调用系统相机录像及实现录音
581 0
|
移动开发 JavaScript Android开发
通过howler.js实现在Android下的微信浏览器自动播放音频
通过howler.js实现在Android下的微信浏览器自动播放音频
399 0
通过howler.js实现在Android下的微信浏览器自动播放音频
|
存储 Dart Java
【Flutter】packages思维以及使用Java添加Android平台特定的实现在Flutter框架里的体现和运用
【Flutter】packages思维以及使用Java添加Android平台特定的实现在Flutter框架里的体现和运用