【凯子哥带你做高仿】“煎蛋”Android版的高仿及优化(二)——大图显示模式、评论“盖楼”效果实现详解

转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992

在前一篇文章中,我们学习了如何进行逆向工程和TcpDump进行抓包,获取我们的数据接口,那么有了数据之后,我们就可以开始代码编写工作了。
本项目在前几天获得了daimajia大神的推荐,star数已经达到115,多谢大家的支持,欢迎提建议和意见。
项目地址:https://github.com/ZhaoKaiQiang/JianDan

目前项目已完成以下功能,本文章将会总结在编码过程中遇到的挑战和解决方案。

项目进度

已完成的功能

  • 查看段子
  • 查看无聊图(静态图和GIF动态图)
  • 查看妹子图(程序员必备)
  • 对段子、无聊图、妹子图进行投票
  • 段子的复制与分享
  • 无聊图、妹子图的保存与分享
  • 查看吐槽与回复
  • 图片详情页的动画效果
  • 添加新鲜事列表页
  • 添加新鲜事详情页

优化的功能

  • 添加加载等待动画
  • 添加加载失败提示
  • 添加段子列表界面,点击标题栏快速返回顶端
  • 添加评论楼层过多隐藏
  • 添加网络状态检测
  • 优化无聊图列表显示,非WIFI状态下,显示GIF缩略图,点击后下载
  • 加载模式全自动智能切换,显著提高加载速度,节省大量流量
  • 修改图片详情页为完全沉浸效果
  • 图片详情页添加投票结果的颜色标示
  • 添加图片列表滚动检测,滚动状态暂停加载,进一步提高加载速度,减少卡顿
  • 添加图片加载图片
  • 添加当前栏目标志,避免重复切换
  • 修改新鲜事列表页效果为CardView

想完成但没有完成的功能

  • 列表加载动画(虽然试过好多次,但是都不能实现首次加载,CardView进入时的动画效果,如果你能知道我如何实现,我将非常感激)
  • 本地缓存(后期将添加)

效果图



项目整体架构介绍

使用的开源框架

项目整体介绍

从上面的效果图也可以看出来,我们使用的是Material Design风格,但是并不纯正,为了兼容4.x版本,我们使用Theme.AppCampat兼容主题、RecycleView和CardView来完成,从整体视觉效果来看比较统一和美观。同时为了整体的效果,使用开源项目material-dialogs来实现Material Design效果的对话框,这个在点击回复,完善个人信息的功能点上有所体现。

除了界面,网络请求框架我选择的是Volley,原因是Volley对小数据量、请求频繁的网络操作进行了优化,对于这个项目比较合适,而且作为Google的推荐项目,现在已经完善的比较成熟了,经过了很多项目的实战验证,所以比较放心。而且扩展性非常强,可以定制我们自己的请求解析需求,这一点相信看过我项目的朋友,应该有所感受,在com.socks.jiandan.net包下的请求类都经过了我的定制,使用方便。而且很重要的一点是,Volley在2.3之后是基于HttpURLConnection的封装实现,默认支持gzip压缩,在4.0之后的版本,还支持结果缓存,所以在性能和数据传输量上,相比HttpClient有很大的提高。

在本项目中一个很重要的功能就是加载图片,所以在图片加载框架上需要特别注意。最初我选择的图片加载框架是Fresco,因为之前翻译过关于Fresco的特性的文章,感觉非常的强大,所以想试一试。但是在后面使用的时候,还是遇到了很多的问题,让我不得不暂时放弃Fresco,改用UIL。原因如下:

  • 推出时间太短,虽然功能强大,但是还没经过考验,还不很成熟。Fresco的更新频率很快,我开始用的时候还是0.1.0版本,后来在加载图片的时候遇到问题,在这个版本上,Fresco没有对有304缓存的图片进行处理,所以在加载这类图片的时候会出现失败,我给Fresco项目提交issue之后,他们回复我,Fresco已经升级,在0.2.0完成了问题修复。所以我觉得,Fresco还需要一段时间的考验和完善,才能被用到生产环境中,现在我不很推荐大家在项目中使用
  • 不支持wrap_content。放弃Fresco的一个很重要的原因就是因为它不支持wrap_content,Fresco只支持match和固定长宽,在这个项目中需要展示大量宽度match,高度不定的图片,因为Fresco显示图片的控件也是自己定制的,所以自定义控件这条路也比较难走,在没有找到更好的解决方案的情况下,我决定暂时放弃Fresco,改用UIL。在本项目中,只有在评论列表页的头像是使用的Fresco,其他地方都是使用UIL和自定义控件实现,具体实现方案我会在下面讲到。

在IOC框架的选择上,使用butter knife,之前一直使用AFinal,但是AFinal属于运行期绑定,会影响性能,butter knife属于编译期绑定,不会影响。使用butter knife使用非常方便,就拿来一用。在本项目中,我感觉其实并不是很需要IOC,仅作一个尝试而已,不必深究。

在完成网络状态切换的功能上,需要在MainActivity注册一个网络状态监听器,当网络状态发生改变的时候,通知当前显示的Fragment切换图片的加载模式,或者是提示网络状态变化情况。在这种需求下,使用接口是可以完成的,每个Fragment都实现MainActivity的一个接口,当网络状态发生变化的时候,MainActivity调用Fragment的接口方法即可。但是这样不仅很麻烦,而且会增加耦合性,为此,我使用EventBus完成了这个功能,实现很简单,大家看源码就可以,耦合度为0。

这个项目中的所有数据接口基本都是Json格式,所以选择一个好的解析框架是很重要的。我之前写过三篇文章介绍了Json的不同解析方法,虽然Jackson的解析速度快,但是gson确实用起来很熟悉,而且我们要解析的数据量并不大,性能上的差异微乎其微,所以我选择了我比较熟悉的gson。在解析的一些地方还用到了一些JSON,这个大家可以自由选择。

项目中遇到的问题及解决方案

加载任意高度的图片

我们在前面介绍Fresco的时候提到过,之所以放弃它,很大的一个原因就是因为这个功能它不支持,我们先来看看我们要实现功能的详细分析。

  • 图片宽度要和ImageView的相同
  • 在上一个条件满足的情况下,完整的显示这个图片,高度自适应

也就是下面的效果

我的解决思路是这样的,宽度和ImageView相同,那么设置为match_parent即可,高度则是wrap_content,但是这样显示之后,图片可以完整显示,但是不能符合我们宽度填充,高度自适应的要求。那么我们可能就要设置ScaleType了,但是在试过了所有类型之后,也都满足不了我们的要求,要不就是只能显示一部分,要不就是宽度不能填充,或者是不能居中显示。为此,我们可以试一试自定义控件。

我们可以设置ScaleType为centerCrop,还记得centerCrop是什么意思么?以图片几何中心为基准,放缩短边至填充满。这样做的话,第一个填充效果就可以实现了,剩下的就是要高度自适应了。

我第一次在做这个功能的时候,走入了一个误区。

第一个思路就是,重写ImageView的setBitmap和setDrawable方法,在设置之后,获取bitmap,然后计算ImageView的宽度和bitmap的比例,以此比例计算bitmap的高度,然后生成新的Bitmap对象,设置给ImageView,设置之后,调用requestLayout(),重新布局,完成高度的改变。首先,使用这个方案是完全能解决问题的,计算完之后,重新布局,可以使得高度自适应,但是,你发现问题了吗?我在计算高度之后,又重新生成了Bitmap对象,而这一步是使用下面的方法完成的

 Matrix matrix = new Matrix();
 matrix.postScale(1.5f,1.5f); //长和宽放大缩小的比例
 Bitmap resizeBmp  = Bitmap.createBitmap(bitmap,0,0,Width,height,matrix,true);

在这个操作里面,使用到了矩阵,而矩阵计算会占用大量cpu时间,因此,当我这么完成之后,慢慢滑动列表是没有问题的,但是当我疯狂的快速滑动的时候,就会出现非常明显的卡顿。

那么怎么解决这个问题呢?其实我后来看代码,完全没必要再生成新的Bitmap,只计算合适的高度就可以完成我们的需求,因此,修改之后的代码如下

/**
 * 自定义控件,用于显示宽度和ImageView相同,高度自适应的图片显示模式.
 * 除此之外,还添加了最大高度限制,若图片长度大于等于屏幕长度,则高度显示为屏幕的1/3
 * Created by zhaokaiqiang on 15/4/20.
 */
public class ShowMaxImageView extends ImageView {
   

    private float mHeight = 0;

    public ShowMaxImageView(Context context) {
        super(context);
    }

    public ShowMaxImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ShowMaxImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void setImageBitmap(Bitmap bm) {

        if (bm != null) {
            getHeight(bm);
        }

        super.setImageBitmap(bm);
        requestLayout();
        invalidate();
    }

    @Override
    public void setImageDrawable(Drawable drawable) {

        if (drawable != null) {
            getHeight(drawableToBitamp(drawable));
        }

        super.setImageDrawable(drawable);
        requestLayout();
        invalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {


        if (mHeight != 0) {

            int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
            int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

            int resultHeight = (int) Math.max(mHeight, sizeHeight);

            if (resultHeight >= ScreenSizeUtil.getScreenHeight((Activity) getContext())) {
                resultHeight = ScreenSizeUtil.getScreenHeight((Activity) getContext()) / 3;
            }

            setMeasuredDimension(sizeWidth, resultHeight);
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }

    }

    private void getHeight(Bitmap bm) {

        float bitmapWidth = bm.getWidth();
        float bitmapHeight = bm.getHeight();

        if (bitmapWidth > 0 && bitmapHeight > 0) {
            float scaleWidth = getWidth() / bitmapWidth;
            if (scaleWidth != 0) {
                mHeight = bitmapHeight * scaleWidth;
            }
        }

    }


    private Bitmap drawableToBitamp(Drawable drawable) {

        if (drawable != null) {
            BitmapDrawable bd = (BitmapDrawable) drawable;
            return bd.getBitmap();
        } else {
            return null;
        }
    }

}

使用上面的代码之后,我们就完成了图片的完整显示,而且没有任何的性能问题,至此,问题解决。

评论的“楼中楼”、“多楼隐藏”效果实现

在讲解具体实现之前,我们需要先了解一下评论列表的数据结构。

我们以这个测试接口为例:http://jandan.duoshuo.com/api/threads/listPosts.json?thread_key=comment-2750904

因为数据太多,我就不在这里粘贴了,大家自己打开看就可以。

煎蛋使用的是多说的评论接口,所以获取接口都是从多说获取。

从上往下的标签意义如下:

  • hotPosts 热门评论
  • thread 当前被评论主体的信息,包括thread_id、thread_key、url、comments等重要数据
  • cursor 总数和评论页码
  • parentPosts 所有的具体评论数据
  • response 参与回复的所有主体的id
  • options 可选属性,暂时无用

我们需要重点关注的是hotPosts、parentPosts。

在了解我们要显示的数据结构之后,我们就要思考如何去实现我们的评论列表的效果。

第一个问题是,如何添加“热门评论”、“最新评论”的分割标志,并对评论进行分类。
这一步我实在自定义Request里面完成的,为了完成这个跟功能,我们需要一个评论的实体类,下面是重要字段

//评论内容标签
    public static final String TAG_HOT = "hot";
    public static final String TAG_NORMAL = "normal";

    //评论布局类型
    public static final int TYPE_HOT = 0;
    public static final int TYPE_NEW = 1;
    public static final int TYPE_NORMAL = 2;

    private String avatar_url;
    private String created_at;
    private String name;
    private String message;

    //评论发送者id
    private String post_id;
    //这条评论所回复的评论id
    private String parent_id;
    //这条评论上的所有评论id
    private String[] parents;
    //所属楼层
    private int floorNum;

    //用于标示是否是热门评论
    private String tag;
    //用于区别布局类型:热门评论、最新评论、普通评论
    private int
  • 16
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 33
    评论
评论 33
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值