Android第三方库CustomShapeImageView(定义形状ImageView)

 更新时间:2016年9月20日 19:53  点击:1354
CustomShapeImageView 是一个可以自己定制ImageView形状的开源库,它通过使用 PorterDuffXfermode API,将图片及可缩放矢量图形(SVGs)结合实现多种可定制形状的ImageView。

CustomShapeImageView是Android开发的第三方类库,具有不限于圆形ImageView的多种形状ImageView,项目开发必备

github下载地址:https://github.com/MostafaGazar/CustomShapeImageView

1、首先源码中有一个第三方类库 :library

先要把Library导入到项目中,

如果你会导入季,直接跳过


关于Eclipse 和 IDEA 导入library库文件 的步骤

这里我们以PullToRefresh(上拉刷新下拉加载)组件的library为例

下载地址:

https://github.com/chrisbanes/Android-PullToRefresh


现在我们需要把library文件夹导入到Eclipse或者IDEA中去

一、IDEA 导入library库文件步骤

1、首先我们要有一个项目,没有的就创建一个吧

2、右击项目名称点击Open Module Settings(F4)


3、可以看到这样的界面


接下来在中间部分 点击绿色的加号 导入Module


找到要导入的library类库的目录


点击OK 后,,新的界面选择 第一个选项 Create module from existing sources,然后下一步知道import操作完成


4、然后就可以看到这样的界面,中间界面 多了一个library文件夹


5、接着点击最右边界面的绿色加号按钮 选择第三个Module Dependency,注意中间部分要选择你要导入library库文件的目录,即此时在中间界面选中demo文件夹,在按绿色按钮添加


6、可以看到有library文件夹可以选择 选择OK就行了 然后OK 结束设置


7、这是就可以看到你的项目里多了一个library文件夹


打开library文件夹可以看到文件夹内容都在,


8、我们在主Activity中添加一个library 提供的类检查是否导入成功,不报错可导入成功


二、Eclipse 导入library库文件步骤

1、导入


2、选择 Android/Existing Android Code Into Workspace


3、选择library文件夹目录 ,记得选中 Copy projects into workspace


4、可以看到项目目录多了library


5、右键library 选择properties (在最下面)

点击is Library --》ok


6、然后右击要导入library库文件的的项目 选择properties 添加Add 选择要导入的library文件夹


7、然后使用library库文件提供的类检测是否导入正确 (注意项目和library库文件需要在同一个目录下,即同一个工作空间)



继续讲解CustomShapeImageView类库


2、源码中res文件夹下有一个raw文件夹 复制到自己的项目中(选择性复制,是一些特殊的图形)

可以看到这里有一堆.svg格式的文件。

SVG可以算是目前最最火热的图片文件格式,这里作者已经给我们写好了几个特殊的图形

如果想要自定义更多形状的话,可以学习下SVG

1、shape_5.svg 五边形


2、shape_circle_2.svg 贝壳形


3、shape_flower.svg 花形


4、shape_heart.svg 心形


5、shape_star 星形1

6、shape_star 星形2

7、shape_star 星形3



3、接下来就是使用了

如果我们使用raw文件夹下的svg写的形状作为ImageView的形状

则:这里一个app:svg_raw_resource="@raw/shape_star_3" 这里就是指定图片形状为res/raw文件夹下的哪一个


<com.meg7.widget.SvgImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/hydrangeas"
app:svg_raw_resource="@raw/shape_star_3"
android:scaleType="centerCrop" />


重点来了:

实际开发,最常用的莫过于圆形图片了,那么上面几个都是特殊图形,要使用圆形ImageView怎么办?

看类库,几个源码


BaseImageView.java 是基本类

而CircleImageView.java类则是继承BaseImageView.java的类,用于圆形ImageView

使用: 这个就简单了,和普通ImageView一样使用,标签变了而已


android:layout_width="100dp"

android:layout_height="100dp"

android:src="@drawable/hydrangeas"

android:scaleType="centerCrop" />


长方形ImageView :RectangleImageView.java


android:layout_width="100dp"

android:layout_height="100dp"

android:src="@drawable/hydrangeas"

android:scaleType="centerCrop" />


以上便是主要需要的部分了。

源码中CustomShapeImageView.java看了下源码是默认显示圆形图片的ImageView,里面有圆形,方形,自定义形,看情况使用了,个人感觉,以上的几个使用起来就足够了。


Android中的动画有很多类型,主要有帧动画,补间动画,属性动画。Fragment是为了适应Android屏幕的尺寸而出现的。

Fragment:

Android运行在各种各样的设备中,有小屏幕的手机,超大屏的平板甚至电视。针对屏幕尺寸的差距,很多情况下,都是先针对手机开发一套App,然后拷贝一份,修改布局以适应平板神马超级大屏的。难道无法做到一个App可以同时适应手机和平板么,当然了,必须有啊。Fragment的出现就是为了解决这样的问题。你可以把Fragment当成Activity的一个界面的一个组成部分,甚至Activity的界面可以完全有不同的Fragment组成,更帅气的是Fragment拥有自己的生命周期和接收、处理用户的事件,这样就不必在Activity写一堆控件的事件处理的代码了。更为重要的是,你可以动态的添加、替换和移除某个Fragment。


Fragment的生命周期,Fragment必须是依存与Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期。


使用Fragment可以让我们更加充分地利用手机的屏幕空间,他可以在一个activity中有多个Fragment,多个Fragment可以显示不同的内容。

我们通过简单的demo来了解什么是Fragment和Fragment如果使用

    在这个demo中我们使用FrameLayout(框架布局),Framelayout一般很少用到,在下面的Demo中使用会很方便,所有添加到这个布局中的视图都以层叠的方式显示。第一个添加的控件被放在最底层,最后一个添加到框架布局中的视图显示在最顶层,上一层的控件会覆盖下一层的控件。

    需求:我们在一个布局文件中的左边竖直分布,三个button,右边是一个Framelayout,当我们点击不同的button时,右边显示不同的Fragment。

    第一步:写布局文件,布局文件左边包含三个按钮,右边是一个FrameLayout。 再写三个填充用的布局文件,这三个布局文件中background的颜色都不一样,有一个textView用来区分不同的布局文件。

    第二不写三个 fragment的Java类  分别将三个布局文件填充到对应的java类中作为内容显示在屏幕母上

public class fragment01 extends Fragment {
 
    //返回的view对象会作为fragment01的内容显示在屏幕上
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
 
        View v = inflater.inflate(R.layout.fragment, null);
        return v;
    }

    第三步 在MainActivity中写对三个Button做处理

        当点击按钮一的时候,屏幕右边显示的是Fragment01

        第一步 new出Fragment01的对象

        第二步  获取Fragment的管理器getFragmentManager()

        第三步  打开事物beginTransaction()

        第四步  把内容显示到帧布局replace 最后提交事物commit

public void click1(View v) {
       fragment01 fg = new fragment01();
       FragmentManager fm  = getFragmentManager();
       FragmentTransaction ft = fm.beginTransaction();
       ft.replace(R.id.fl,fg );
       ft.commit();
   }

 

fragment是3.0之后出来的,我们可以做向下兼容,让低版本可以使用。这个时候就用到了安卓支持类库。 我们在引包的时候都改成support的包但是要注意的是,但是在获去fragment管理器,没有支持类库,我们可以使用getSupportFragmentManager()来获得兼容低版本的管理器


在Fragment和activity中传递数据

把Fragment中的数据传递到activity中做法:
在Fragment的xml文件中,放一个EditText。还有一个button 但我们点击button的时候把数据传递到 activity的EditText中。
把activity中的数据传递到Fragment中

      步骤:1 我们在fragment中的布局文件中,定义一个输入框, 在定义一个button,但是onClick属性只能在上下文中使用,在Fragment中不可以使用

我们可以给按钮设置点击侦听

          2 在MainActivity的布局文件中,定义一个TextView 用来显示数据

          3我们在MainActivity定义一个方法,用来给TextView 设置显示的数据

          4 在Fragment的java类中 我们拿到button 给button设置监听,在按钮点击的时候,拿到输入框中的数据通过getActivity()方法将它强转成MainActivity就可以拿到和Fragment相关联的activity,这个时候就可以调用activity中的方法给textview设置值了。

注意 :如果将fragment和activity中的EditText和TextView的id设置成相同,虽然findViewById是在各自的布局文件中拿,但是由于Activity中给TextView设置值得方法是在fragment中调用,那么Activity中的方法会优先在fragment的布局文件中找,而不再自己的布局文件中寻找。
如果两个id相同的话解决方案是,在activity中把拿去布局文件的方法变量提成全局,在activity创建方法中取拿到

   把activity中的数据传递到Fragment中的做法:

      步骤:1 在fragment的xml文件中定义一个TextView,用来显示数据。

         2 我们在fragment的java类中拿到 定义的TextView, 创建一个公开的方法,这个方法内可以个TextView设置值

         3 我们在MainActivity中定义一个EditText用来输入数据,定义一个button,点击的时候传递数据

         4 当我们点击按钮的时候我们拿到EditText中的数据,同时new出对应要传递的fragment的java类的对象,直接用fragment对象调用它的方法传值。

 

fragment的生命周期方法和activity的生命周期方法相同并且是绑定的,fragment切换时旧fragment对象会销毁,新的fragment对象会被创建

 

帧动画:

    一张张图片不断的切换,形成动画效果,安卓手机的开机界面就是通过帧动画做的

     如何自己使用帧动画:

        步骤:1将素材拷贝到drawable中

           2 在drawable目录下定义xml文件(在api中的drawable Animation中可以看到xml文件的格式和使用代码)

<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> //这里的false表示循环播放 true表示只播放一次
            <item android:drawable="@drawable/g1" android:duration="200" />//表示duration表示每张图片显示的时长
            <item android:drawable="@drawable/g2" android:duration="200" />
            <item android:drawable="@drawable/g3" android:duration="200" />
        </animation-list>

 

          3 在屏幕上播放帧动画

ImageView iv = (ImageView) findViewById(R.id.iv); //首先拿到imageView 将动画播放在imageView
   //把动画文件设置为imageView的背景
   iv.setBackgroundResource(R.drawable.animations); ////把帧动画的资源文件指定为iv的背景
   AnimationDrawable ad = (AnimationDrawable) iv.getBackground();//获取iv的北京
   //播放动画       
   ad.start();

 

补间动画:

原形态变成新形态时为了过渡变形过程,生成的动画就叫补间动画  位移、旋转、缩放、透明

  位移
    
/*位移:
    参数10指的是X的起点坐标,但不是指屏幕x坐标为10的位置,而是imageview的 真实X + 10
     参数150指的是X的终点坐标,它的值是imageview的 真实X + 150
     
        //创建为位移动画对象,设置动画的初始位置和结束位置
        //TranslateAnimation ta = new TranslateAnimation(10, 150, 20, 140);
* x坐标的起点位置,如果相对于自己,传0.5f,那么起点坐标就是 真实X + 0.5 * iv宽度
* x坐标的终点位置,如果传入2,那么终点坐标就是 真实X + 2 * iv的宽度
* y坐标的起点位置,如果传入0.5f,那么起点坐标就是 真实Y + 0.5 * iv高度
* y坐标的终点位置,如果传入2,那么终点坐标就是 真实Y + 2 * iv高度*/
     
        TranslateAnimation ta = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 2, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 2);
        //设置动画持续时间
        ta.setDuration(2000);
        //动画重复播放的次数
        ta.setRepeatCount(1);
        //动画重复播放的模式
        ta.setRepeatMode(Animation.REVERSE);
        //动画播放完毕后,组件停留在动画结束的位置上
        ta.setFillAfter(true);
        //播放动画
        iv.startAnimation(ta); //iv表示动画显示显示在哪个地方 通过findviewbyid拿到

 

  旋转  

public void rotate(View v){<br>          //表示从0到720旋转                            表示从真实的x+图片的宽度的一半 在中心旋转
        RotateAnimation ra = new RotateAnimation(0, 720, Animation.RELATIVE_TO_SELF, 0.5f,
        //设置动画持续时间                              Animation.RELATIVE_TO_SELF, 0.5f);
        ra.setDuration(2000);<br>         //设置动画重发次数表示重发播放两次
        ra.setRepeatCount(1);
        ra.setRepeatMode(Animation.REVERSE);
        iv.startAnimation(ra);
    }

 缩放

public void scale(View v){
//      sa = new ScaleAnimation(fromX, toX, fromY, toY, iv.getWidth() / 2, iv.getHeight() / 2);
        sa = new ScaleAnimation(0.5f, 2, 0.1f, 3, Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f);  //o.5f表示动画的起始宽度是真实宽度的0.5倍 2表示动画结束宽度是真实宽度的2倍  后面参数表示旋转中心点的位置 默认是坐上角
        sa.setDuration(2000);
        //填充动画的结束位置
        sa.setRepeatCount(1);
        sa.setRepeatMode(Animation.REVERSE);
        sa.setFillAfter(true);
        iv.startAnimation(sa);
    }

 透明

public void alpha(View v){
        aa = new AlphaAnimation(0, 1); //0表示完全透明。1表示不透明
        aa.setDuration(2000);
        sa.setRepeatCount(1);
        iv.startAnimation(aa);
    }

  我们可以设置所有的动画一起播放

public void fly(View v){
        AnimationSet set = new AnimationSet(false);//false表示使用自己的叫对器
        set.addAnimation(ta);
        set.addAnimation(sa);
        set.addAnimation(ra);
        set.addAnimation(aa);
         
        iv.startAnimation(set);
    }

  

 

属性动画
    补间动画,只是一个动画效果,组件其实还在原来的位置上,xy没有改变。而属性动画是xy真实改变了,属性动画是3.0后加入的特性。没有向下支持类库

 位移:

public void translate(View v){
         
        //target:动画作用于哪个组件  第一个参数target指定要显示动画的组件  第二个参数propertyName指定要改变组件的哪个属性  第三个参数values是可变参数,就是赋予属性的新的值
        ObjectAnimator oa = ObjectAnimator.ofFloat(iv, "translationX", 10, 70, 20, 100); //他可以实现来回移动 我们可以动过set和get来看属性是什么
        oa.setDuration(2000);
        oa.setRepeatCount(1);
        oa.setRepeatMode(ValueAnimator.REVERSE);
        oa.start();
    }

  缩放

public void scale(View v){
        ObjectAnimator oa = ObjectAnimator.ofFloat(iv, "scaleX", 1, 1.6f, 1.2f, 2);
        oa.setDuration(2000);
        oa.start();
    }

  

透明:

public void alpha(View v){
        ObjectAnimator oa = ObjectAnimator.ofFloat(iv, "alpha", 0, 0.5f, 0.2f, 1);
        oa.setDuration(2000);
        oa.start();
    }

旋转


public void rotate(View v){
        ObjectAnimator oa = ObjectAnimator.ofFloat(iv, "rotationY", 0, 180, 90, 360); //rotation指定是顺时针旋转

//属性指定为rotationX是竖直翻转
//属性指定为rotationY是水平翻转

    oa.setDuration(2000);
    oa.setRepeatCount(1);
    oa.setRepeatMode(ValueAnimator.REVERSE);
    oa.start();
}

所有动画一起播放  

//创建动画师集合
        AnimatorSet set = new AnimatorSet();
        //设置要播放动画的组件
        set.setTarget(bt);
        //所有动画有先后顺序的播放
        //set.playSequentially(oa, oa2, oa3, oa4);
        //所有动画一起播放
        set.playTogether(oa, oa2, oa3, oa4);
        set.start();

使用xml文件定义属性动画

<?xml version="1.0" encoding="utf-8"?>  在animator文件下 resource type 选择property animator root element是set
<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <objectAnimator
        android:propertyName="translationX"
        android:duration="200"
        android:repeatCount="1"
        android:repeatMode="reverse"
        android:valueFrom="-100"
        android:valueTo="100"
        >
         
    </objectAnimator>
</set>


public void xml(View v){
        Animator at = AnimatorInflater.loadAnimator(this, R.animator.objanimator); //加载指定xml
        //设置作用于哪个组件
        at.setTarget(iv);
        at.start();
    }



关于Android的屏幕适应Fragment及各类动画的类型介绍,我们就讲到这里

在Android开发,性能是一个大问题,弄不好会使内存崩溃。本文我们分享总结的一些 Android 性能调优的技术点,Android如何做内存性能优化。

Android性能调优涉及到多方面的工作,因本人技术水平有限,目前只总结了以下部分,希望大家继续补充。

要点

使用异步

    保持 APP 的高度响应,不要在 UI 线程做耗时操作,多使用异步任务
    使用线程时要做好线程控制;使用队列、线程池
    谨慎使用糟糕的 AysncTask 、 Timer
    警惕异步任务引起的内存泄露
    应该异步任务分类,比如 HTTP ,图片下载,文件读写,每一类的异步任务维护一个任务队列,而不是每一个任务都开一个线程( Volley 表示我一个可以搞定这些全部 _(:з」∠)_)
    这些常用的任务应该做好优先级处理(一般 JSON 数据优先于图片等静态数据的请求)
    一般异步任务应该开启一个 SingleAsyncTask ,保证一时只有一个线程在工作
    HTTP 和图片下载尽量使用同一套网络请求
    使用 MVP 模式规范大型 Activity 类的行为,避免异步任务造成的内存泄露

避免内存泄露

    了解虚拟机内存回收机制
    频繁 GC 也会造成卡顿,避免不必要的内存开销
    错误的引用姿♂势造成的内存泄露(啊~要泄了~)
    常见的 Activity 泄露(单例、 Application 、后台线程、无限动画、静态引用)
    Bitmap 泄露( HoneyComb 这个问题之前压力好大)
    尽量使用 IntentService 代替 Service ,前者会自动 StopItself
    排查内存泄露问题的方法(我一直以来都是简单暴力的人肉 dump 检查大法)
    使用 LeakCanary 自动检查 Activity 泄露问题
    对内存负载要保持敏感( Sharp )

视图优化

    布局优化、减少层次, Include Merge
    使用 ViewStub 避免不必要的 LayoutInflate ,使用 GONE 代替重复 LayoutInflate 同一个布局
    避免过度绘制,应该减少不必要的布局背景;布局层次太深会造成过度绘制以及 Measure 、 Layout 等方法时间复杂度的指数增长
    使用过渡动画,比如给图片的呈现加一个轻量的淡入效果会让视觉上变得流畅许多
    避免过度的动画,不要让一个界面同时出现多出动画,比如 List 滚动时 Item 项要停止播放动画或者 GIF
    复杂动画使用 SurfaceView 或 TextureView
    尽量提供多套分辨率的图片,使用矢量图

Adapter 优化

    复用 convertView ,用 ViewHolder 代替频繁 findViewById
    不要重复 setListener ,要使用 v.getId 来复用 Listener ,不然会创建一堆 Listener 导致频繁 GC
    多布局要采用 MutilItemView ,而不是使用一个大布局然后动态控制需要现实的部分
    不要在 getView 方法做做耗时的操作
    快速滚动列表的时候,可以停止加载列表项的图片,停止列表项的动画,不要在这时候改变列表项的布局
    尽量用 RecyclerView (增量 Notify 和 RecycledViewPool 带你飞)

代码优化

    算法优化,减少时间复杂度,参考一些经典的优化算法
    尽量使用 int ,而不是 float 或者 double
    尽量采用基本类型,避免无必要的自动装箱和拆箱,浪费时间和空间
    选用合适的集合类(尽量以空间换时间)、选用 Android 家的 SparseArray,SparseBooleanArray 和 LongSparseArray
    避免创建额外的对象( StringBuilder )
    使用 SO 库完成一些比较独立的功能(高斯模糊)
    预处理(提前操作)一些比较耗时的初始化工作统一放到启动图处理
    懒加载(延迟处理)规避 Activity 的敏感生命周期
    Log 工具类,要在编译时删掉调试代码,而不是在运行时通过判断条件规避
    优先使用静态方法、公有方法还是公有方法?速度区别很大哦
    类内部直接对成员变量进行操作,不要使用 getter/setter 方法,调用方法耗额外的时间
    给内部类访问的外部类成员变量要声明称包内可访问,而不是私有,不然编译的时候还是会自动创建用于访问外部类成员变量的方法
    遍历集合时,使用 i++代替 Iterator ,后者需要额外的对象操作,应在循环体内避免这种情况
    如果一个基本类型或者 String 的值不会改变,尽量用 final static ,编译时会直接用变量的值替换变量,也就不需要在查询变量的值了

其他优化

    数据库优化:使用索引、使用异步线程
    网络优化 …… 一堆优秀的轮子
    避免过度使用依赖注入框架,大量的反射
    不过过度设计 /抽象,多态看起来很有设计感,代价就是额外的代码、空间、时间
    尽量不要开启多进程,进程的开销很大

APK 瘦身

    开启混淆
    使用 zipalign 工具优化 APK
    适当有损图片压缩、使用矢量图
    删除项目中冗余的资源,之前写过一些删除没有 res 资源的脚本
    动态加载模块化,项目拆分啊!

性能问题的排查方法

    GPU 条形图,没事开来看看淘宝
    过度绘制颜色,嗯,不要一篇姨妈红就好
    LeakCanary ,自动检测 Activity 泄露,挺好用的
    TraceView ( Device Monitor ), Systrace ,分析哪些代码占用的 CPU 时间太大,屡试不爽
    Lint ,检查不合理的 res 资源
    layoutopt (还是 optlayout ?),对当前布局提出优化建议,已被 lint 替代,但是还能用
    HierarchyViewer ,查看手机当前界面的布局层次,布局优化时常用(只用于模拟器,真机上用要 ROOT ,不想 ROOT 加得使用 ViewServer )
    StrictMode , UI 操作、网络操作等容易出现性能问题的地方,如果出现异常情况 StrictMode 会报警


Android内存性能优化


刚入门的童鞋肯能都会有一个疑问,Java不是有虚拟机了么,内存会自动化管理,我们就不必要手动的释放资源了,反正系统会给我们完成。其实Java中没有指针的概念,但是指针的使用方式依然存在,一味的依赖系统的gc,很容易就造成了内存的浪费。


Java基于垃圾回收的内存机制


Java的内存管理机制会自动回收无用对象所占用的内存,减轻手工管理内存的负担

1、C/C++: 从申请、使用、释放都需要手工管理

2、Java:无用的对象的内存会被自动回收


\

什么样的对象是无用的对象

1、Java通过引用来操作一个具体的对象,引用类似于C 中的指针。一个对象可以持有其他对象的引用。

2、从一组根对象(GC Roots)开始,按对象之前的引用关系遍历所有对象,在遍历过程中标记所有的可达对象。如果一个对象由根对象出发不可达,则将它作为垃圾收集。

GCRoot 都有哪些?

1、 Class:由系统的类加载器加载的类对象

2、 Static Fields

3、 Thread:活着的线程

4、 Stack Local: java方法的局部变量或参数

5、 JNI Local: JNI方法中的局部引用

6、 JNI Global: 全局的JNI引用

7、 Monitor used: 用于同步的监控对象

8、Help by VM: 用于JVM特殊目的由GC保留的对象


\


\


\

Java程序中的内存泄漏

对象的内存在分配之后无法通过程序的执行逻辑释放对该对象的引用,不能被回收该对象所占内存

内存泄漏的危害

1、 引起OutOfMemoryError

2、 内存占用高时JVM虚拟机会频繁触发GC, 影响程序响应速度

3、内存占用大的程序容易被各种清理优化程序中止,用户也更倾向于卸载这些程序

Android应用的开发语言为Java,每个应用最大可使用的堆内存受到Android系统的限制

Android每一个应用的堆内存大小有限

1、 通常的情况为16M-48M

2、 通过ActivityManager的getMemoryClass()来查询可用堆内存限制

3、3.0(HoneyComb)以上的版本可以通过largeHeap=“true”来申请更多的堆内存

Nexus S(4.2.1):normal 192, largeHeap 512

4、如果试图申请的内存大于当前余下的堆内存就会引发OutOfMemoryError()

5、应用程序由于各方面的限制,需要注意减少内存占用,避免出现内存泄漏。

用MAT工具来检测内存泄漏

在试图窗口中新建一个Memory Analysis会出现一个


\

没有的可以去http://www.eclipse.org/mat/downloads.php安装一下MAT

在Android 的调试环境DDMS下,找到Heap dump


\


\

Dump下当前内存中的镜像文件,*****.hprof


\

能清楚的看到每一个部分暂用的内存大小。

也可以切换试图,group查看不同包不同类的占用细节。


\

Heap dump

? 包含了触发Heap dump生成的时刻Java进程的内存快照,主要内容为各个Java类和对象在堆内存中的分配情况

Memory Analyzer Tool (MAT)


常见内存泄露原因

Context对象泄漏

1、如果一个类持有Context对象的强引用,就需要检查其生存周期是否比Context对象更长。否则就可能发生Context泄漏。

2、View持有其创建所在Context对象的引用,如果将View对象传递给其它生存周期比View所在Context更长的强引用,就可能会引起内存泄漏。

例如View#setTag(int, Object)的内存泄漏https://code.google.com/p/android/issues/detail?id=18273

3、把Context对象赋给static变量。

避免Context对象泄漏Checklist

1、检查所有持有对Context对象强引用的对象的生命周期是否超出其所持有的Context对象的生命周期。

2、检查有没有把View传出到View所在Context之外的地方,如果有的话就需要检查生命周期。

3、工具类中最好不要有Context成员变量,尽量在调用函数时直接通过调用参数传入。如果必须有Context成员变量时,可以考虑使用WeakReference来引用Context对象。

4、View持有其创建所在Context对象的引用,如果将View对象传递给其它生存周期比View所在Context更长的强引用,就可能会引起内存泄漏。

5、 检查把Context或者View对象赋给static变量的地方,看是否有Context泄漏。

6、检查所有把View放入容器类的地方(特别是static容器类),看是否有内存泄漏。7、使用WeakHashMap也需要注意有没有value-key的引用。

7、尽量使用ApplicationContext。

Handler对象泄漏

1、发送到Handler的Message实际上是加入到了主线程的消息队列等待处理,每一个Message持有其目标Handler的强引用。

如我们通常使用的匿名内部类Handler


HandlermHandler = new Handler() {
    @Override
    public voidhandleMessage(Message msg) {
       mImageView.setImageBitmap(mBitmap);
    }
}


上面是一段简单的Handler的使用。当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用,因为View会依附着一个Activity。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给 Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条 Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

当然,应为是Handler对外部持有引用的原因,我们就可以将Activity设置为一个弱引用,在不必要的时候,不再执行内部方法。



/**
 * @author zhoushengtao
 * @since 2013-12-16 下午3:25:36
 */
 
import android.app.Activity;
importandroid.content.Context;
importandroid.os.Handler;
importandroid.os.Message;
 
importjava.lang.ref.WeakReference;
 
publicclass WeakRefHandler extends Handler
{
    WeakReference<context> mWeakContext;
 
    public WeakRefHandler(Context context)
    {
        mWeakContext = newWeakReference<context>(context);
    }
 
    @Override
    public void handleMessage(Message msg)
    {
        if((mWeakContext.get() instanceofActivity )&& ((Activity)mWeakContext.get()).isFinishing())
                return ;
        if(mWeakContext==null){
            return ;
        }
        super.handleMessage(msg);
    }
}


2、Non-staticinner class 和anonymous class持有其outer class的引用。


Drawable.Callback引起的内存泄漏

Drawable对象持有Drawable.callback的引用。当把一个Drawable对象设置到一个View时,Drawable对象会持有该View的引用作为Drawable.Callback


\

避免Drawable.Callback引起内存泄漏

? 尽量不要在static成员中保存Drawable对象

? 对于需要保存的Drawable对象, 在需要时调用Drawable#setCallback(null).


\

其他内存泄漏<??#65533;"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPjwvcD4KPHAgYWxpZ249"left"> 1、Android DigitalClock引起的内存泄漏http://code.google.com/p/android/issues/detail?id=17015

2、使用Map容器类时,作为Key 的类没有正确的实现hashCode和equal函数

其他内存泄漏

? JNI程序中的内存泄漏

1、 Malloc/free。

2、 JNI Global reference

? Thread-Local Variable

1、 相当于Thread对象的成员变量, 可以存储线程相关的状态

2、 如果thread是alive状态,那么Thread-Local中的对象就无法被GC。

进程内存占用监测工具

Dumpsys

? $ dumpsys meminfo [pid]


\

Procrank + Shell脚本

? #procrank

1、 VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)

2、 RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)

3、 PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)

4、 USS - Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)


\

Shell脚本

#!/bin/bash

while true; do

adbshell procrank " grep "com.qihoo360.mobilesafe"

sleep1

done


当然,部分机型的sh都是经过第三方手机商精简过的,很多命令都用不了。Procrank,就是一个经常被精简掉的命令。

鉴于此:

自己写了一个小工具,检测内存的实时变化,

Github地址:https://github.com/stchou/JasonTest


\


\

小结

1. 保存对象前要三思

I. 对象本身有无隐含的引用

II. 保存后何时能够回收

2. 要了解常见的隐含引用

I. anonymous class outer class

II. View to context

3. 要通过各种工具检查内存占用是否有异常

4. 创建大对象时,要检查它的生命周期


本文分享在Android开发中使用TextView实现实现文字和图片混排(文字环绕图片)效果的实例,后面还有一个支持Span折叠的实例。

在平时我们做项目中,或许有要对一张图片或者某一个东西进行文字和图片说明,这时候要求排版美观,所以会出现文字和图片混排的情况,如图:

Android实现文字和图片混排(文字环绕图片)效果


这种情况就是上下两个文字说明是连续在一起的,这就要求我们计算上面的文字说明怎么和下面的文字说明连贯结合在一起呢,这就要求我们进行计算了,下面给出代码,代码中也有详细的注释,原理也很简单。

因为算是比较简单,直接就在activity中去计算了:

package com.example.test;
import android.app.Activity;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.TextView;
public class MainActivity extends Activity {
  boolean imageMeasured = false;
  TextView tv_right;
  TextView tv_bottom;
  static final String text = "叶凡:小说主角,与众老同学在泰山聚会时一同被九龙拉棺带离地球," +
      "进入北斗星域,得知自己是荒古圣叶凡 叶凡体。历险禁地,习得源术,斗圣地世家,战太古生物," +
      "重组天庭,叶凡辗转四方得到许多际遇和挑战,功力激增,眼界也渐渐开阔。一个浩大的仙侠世界," +
      "就以他的视角在读者面前展开。姬紫月:姬家小姐,出场年龄十七岁。被叶凡劫持一同经历青铜古殿历险," +
      "依靠碎裂的神光遁符解除禁制,反过来挟持叶凡一同进入太玄派寻找秘术。" +
      "在叶凡逃离太玄后姬紫月在孔雀王之乱中被华云飞追杀,又与叶凡[2]相遇,被叶凡护送回姬家" +
      ",渐渐对叶凡产生微妙感情。后成为叶凡的妻子,千载后于飞仙星成仙,在叶凡也进入仙路后再见庞博:" +
      "叶凡大学时最好的朋友,壮硕魁伟,直率义气。到达北斗星域后因服用了圣果被灵墟洞天作为仙苗," +
      "在青帝坟墓处为青帝十九代孙附体离去,肉身被锤炼至四极境界。后叶凡与黑皇镇压老妖神识," +
      "庞博重新掌控自己身躯,取得妖帝古经和老妖本体祭炼成的青莲法宝,习得妖帝九斩和天妖八式," +
      "但仍伪装成老妖留在妖族。出关后找上叶凡,多次与他共进退。星空古路开启后由此离开北斗," +
      "被叶凡从妖皇墓中救出,得叶凡授予者字秘、一气化三清,与叶凡同闯试炼古路,一起建设天庭";
  // 屏幕的高度
  int screenWidth = 0;
  // 总共可以放多少个字
  int count = 0;
  // textView全部字符的宽度
  float textTotalWidth = 0.0f;
  // textView一个字的宽度
  float textWidth = 0.0f;
  Paint paint = new Paint();
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    tv_right = (TextView) findViewById(R.id.test_tv_right);
    tv_bottom = (TextView) findViewById(R.id.test_tv_bottom);
    final ImageView imageView = (ImageView) findViewById(R.id.test_image);
    imageView.setImageResource(R.drawable.ee);
    screenWidth = getWindowManager().getDefaultDisplay().getWidth();
    /**
     * 获取一个字的宽度
     */
    textWidth = tv_right.getTextSize();
    paint.setTextSize(textWidth);
    /**
     * 因为图片一开始的时候,高度是测量不出来的,通过增加一个监听器,即可获取其图片的高度和长度
     */
    ViewTreeObserver vto = imageView.getViewTreeObserver();
    vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
      public boolean onPreDraw() {
        if (!imageMeasured) {
          imageMeasured = true;
          int height = imageView.getMeasuredHeight();
          int width = imageView.getMeasuredWidth();
          drawImageViewDone(width, height);
        }
        return imageMeasured;
      }
    });
  }
  private void drawImageViewDone(int width, int height) {
    // 一行字体的高度
    int lineHeight = tv_right.getLineHeight();
    // 可以放多少行
    int lineCount = (int) Math.ceil((double) height / (double) lineHeight);
    // 一行的宽度
    float rowWidth = screenWidth - width - tv_right.getPaddingLeft() - tv_right.getPaddingRight();
    // 一行可以放多少个字
    int columnCount = (int) (rowWidth / textWidth);
    // 总共字体数等于 行数*每行个数
    count = lineCount * columnCount;
    // 一个TextView中所有字符串的宽度和(字体数*每个字的宽度)
    textTotalWidth = (float) ((float) count * textWidth);
    measureText();
    tv_right.setText(text.substring(0, count));
    // 检查行数是否大于设定的行数,如果大于的话,就每次减少一个字符,重新计算行数与设定的一致
    while (tv_right.getLineCount() > lineCount) {
      count -= 1;
      tv_right.setText(text.substring(0, count));
    }
    tv_bottom.setPadding(0, lineCount * lineHeight - height, 0, 0);
    tv_bottom.setText(text.substring(count));
  }
  /**
   * 测量已经填充的长度,计算其剩下的长度
   */
  private void measureText() {
    String string = text.substring(0, count);
    float size = paint.measureText(string);
    int remainCount = (int) ((textTotalWidth - size) / textWidth);
    if (remainCount > 0) {
      count += remainCount;
      measureText();
    }
  }
}

其中xml文件布局如下:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="vertical" >
  <RelativeLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >
    <ImageView
      android:id="@+id/test_image"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:scaleType="fitXY" />
    <TextView
      android:id="@+id/test_tv_right"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:layout_toRightOf="@id/test_image"
      android:gravity="fill_horizontal"
      android:paddingLeft="7dp"
      android:textSize="16sp" />
    <TextView
      android:id="@+id/test_tv_bottom"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:layout_below="@id/test_image"
      android:gravity="fill_horizontal"
      android:textSize="16sp" />
  </RelativeLayout>
</ScrollView>

代码很少,原理也很简单,后来发现这种做法在大部分手机运行是完美的,但是少部分手机还是有点问题。是什么问题呢,是在我们测量textView的长度的是,因为是我们刚刚进行setText,然后马上进行测量,这样得到的结果是不正确的,所以大家可以优化一下。温馨提示,当我们setText之后,可以延时一些时间再去测量,这样获取的值就是挣钱的了,当然那个延迟的时间很短50毫秒就可以了,因为我们要相信textView的绘制速度还是很快的。



Android 文字环绕 图文混排 支持Span折叠

先直接上效果图


http://s5.sinaimg.cn/middle/47021dd4gb63d6d54ffa4&690


Android <wbr>文字环绕 <wbr>图文混排 <wbr>支持Span折叠


上图为实现目标,实现了Android图文混排,文字环绕,支持Span的识别,表情的嵌入,支持文字字体大小的设置等。

 


由于项目中需要用到图文混排技术,在此稍微研究了两天,出来一个效果还算不错的东西

图文混排技术,在不少Android应用中都已经实现,说穿了其实就是两个TextView加一个ImageView的布局罢了,代码里面实现下String的剪切就可以了,不过我这里的这个除了要实现混排效果外,还要支持Span,支持表情等,这就有点麻烦了。下面慢慢分解。先贴出RichTextImageView的布局。
    
<?xml version="1.0" encoding="utf-8"?>
<!-- com.demonzym.richtextdemo.RichTextImageView -->
<com.demonzym.richtextdemo.RichTextImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:id="@+id/richview" >

    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >
       
        <RelativeLayout
            android:id="@+id/layout_preimage_isgif_left"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="0"
            android:visibility="gone" >

            <ImageView
                android:id="@+id/preimage_statues_left"
                android:layout_width="134dip"
                android:layout_height="134dip"
                android:background="@drawable/preview_back"
                android:cropToPadding="true"
                android:scaleType="centerCrop"
                android:src="@drawable/preview_back_small" />

            <ImageView
                android:id="@+id/preimage_isgif_left"
                android:layout_width="24dip"
                android:layout_height="17dip"
                android:layout_alignBottom="@+id/preimage_statues"
                android:layout_alignRight="@+id/preimage_statues"
                android:layout_marginBottom="9dip"
                android:layout_marginRight="7dip" />
        </RelativeLayout>

        <com.demonzym.richtextdemo.RichTextView
            android:id="@+id/lefttext"
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:layout_weight="1"/>

        <RelativeLayout
            android:id="@+id/layout_preimage_isgif_right"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="0"
            android:visibility="gone" >

            <ImageView
                android:id="@+id/preimage_statues_right"
                android:layout_width="134dip"
                android:layout_height="134dip"
                android:background="@drawable/preview_back"
                android:cropToPadding="true"
                android:scaleType="centerCrop"
                android:src="@drawable/preview_back_small" />

            <ImageView
                android:id="@+id/preimage_isgif_right"
                android:layout_width="24dip"
                android:layout_height="17dip"
                android:layout_alignBottom="@+id/preimage_statues"
                android:layout_alignRight="@+id/preimage_statues"
                android:layout_marginBottom="9dip"
                android:layout_marginRight="7dip" />
        </RelativeLayout>
    </LinearLayout>

    <com.demonzym.richtextdemo.RichTextView
        android:id="@+id/bottomtext"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>

</com.demonzym.richtextdemo.RichTextImageView>

 

布局中的RichTextView是另外封装的一个实现Span的TextView,就是实现效果图中表情啊,@啊,#话题# 之类的,了解Span的都懂的,就不细说了。读者研究这个图文混排的时候,可以直接用TextView替代。

 

RichTextImageView  即本文中的图文混排的View。继承于LinearLayout,该类实现的关键代码如下:

 

public void setText(String t) {
  mTopText.setText("");
  mBottomText.setText("");
  mTopText.setVisibility(View.INVISIBLE);
  mBottomText.setVisibility(View.GONE);
  bRequestBottomLayout = true;

  if (t == null)
   t = "";

//  Log.e("setText", t);
 
  mTextContent = t;

  mTopText.setText(mTextContent);

  requestLayout();
 
 }

 


public void setImage(int id) {
  mImageContent = id;
  mPreviewImage.setImageResource(id);
  mPreview.setVisibility(View.VISIBLE);

  if (!Util.isStringEmpty(mTextContent))
   setText(mTextContent);
 }

 

private void iniViews() {
  mLinearLayout = (LinearLayout) findViewById(R.id.linearLayout1);
  mTopText = (RichTextView) findViewById(R.id.lefttext);
  mBottomText = (RichTextView) findViewById(R.id.bottomtext);

  if(IMAGE_LOCATION == IMAGE_LOCATION_RIGHT){
   mPreview = (RelativeLayout) findViewById(R.id.layout_preimage_isgif_right);
   mPreviewImage = (ImageView) findViewById(R.id.preimage_statues_right);
   mPreviewImageIsGif = (ImageView) findViewById(R.id.preimage_isgif_right);
  }else if(IMAGE_LOCATION == IMAGE_LOCATION_LEFT){
   mPreview = (RelativeLayout) findViewById(R.id.layout_preimage_isgif_left);
   mPreviewImage = (ImageView) findViewById(R.id.preimage_statues_left);
   mPreviewImageIsGif = (ImageView) findViewById(R.id.preimage_isgif_left);   
  }
 }
 


 public void setImageLocation(int l){
  if(l != IMAGE_LOCATION_RIGHT && l != IMAGE_LOCATION_LEFT)
   return;
  else{
   IMAGE_LOCATION = l;
   mPreview.setVisibility(View.GONE);
   iniViews();
   setImage(mImageContent);
  }
 }

 


@Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
  // TODO Auto-generated method stub
  super.onLayout(changed, l, t, r, b);

  if (Util.isStringEmpty(mTextContent))
   return;

//  Log.e("top text 宽 高",
//    mTopText.getWidth() + " " + mTopText.getHeight());
//  Log.e("top text 单行高度", "" + mTopText.getLineHeight());
 
//  Rect rect = getStringRect(mTextContent, mTopText.getPaint());
//  Log.e("文本的宽 高", rect.width() + " " + rect.height());
 
  // toptext最多能显示的行数
  int topTextMaxRows = mTopText.getHeight() / mTopText.getLineHeight();
    
  // 显示这些内容真实占用的行数
  int textRows = mTopText.getLineCount();
//  Log.e("top text最多能显示的行数", "" + topTextMaxRows);
//  Log.e("当前文本实际占用的行数", "" + textRows);
 
  //由于文本可能带有表情,重新计算显示行数,以保证显示正确
  Rect lineR = new Rect();
  int realH = 0;     //toptext真实高度
  int i = 0;
  for(i = 0; i < topTextMaxRows; i++){
   try{
    mTopText.getLineBounds(i, lineR);
   }catch(IndexOutOfBoundsException e){
    break;
   }
   realH += lineR.height();
   if(realH >= mTopText.getHeight())
    break;
  }
//  Log.e("当前view实际能显示的行数为", "" + i);
  topTextMaxRows = i;
 
  //如果toptext显示不下的话,显示到bottomtext里面去
  if (textRows >= topTextMaxRows && bRequestBottomLayout) {
   // toptext最后一个可见字符的位置
   int lastindex = mTopText.getLayout().getLineVisibleEnd(
     topTextMaxRows - 1);

   
   
   ClickableSpan[] cs = mTopText.getSpans();
   int spanstart = 0;
   int spanend = 0;
   spanstring = "";
   STATE = 0; // 1网页 2人名 3话题
   for (ClickableSpan c : cs) {
    spanstart = mTopText.getSpanStart(c);
    spanend = mTopText.getSpanEnd(c);
    if (spanstart <= lastindex && spanend > lastindex) {
     if (c instanceof LinkClickableSpan) {
//      Log.e("转角span类型", "网页");
      spanstring = ((LinkClickableSpan) c).getLink();
      STATE = 1;
     }
     if (c instanceof NameClickableSpan) {
//      Log.e("转角span类型", "人名");
      spanstring = ((NameClickableSpan) c).getName();
      STATE = 2;
     }
     if (c instanceof TopicClickableSpan) {
//      Log.e("转角span类型", "话题");
      spanstring = ((TopicClickableSpan) c).getTopic();
      STATE = 3;
     }
     break;
    }
   }
   

   mTopText.setText(mTextContent.substring(0, lastindex));
   mTopText.setVisibility(View.VISIBLE);
   // 当行数不是整数时,调整内容会有下面半行没遮住,所以干脆都处理完以后再显示出来
   mBottomText.setText(mTextContent.substring(lastindex,
     mTextContent.length()) + mBottomText.getText().toString());
   if (mBottomText.getText().length() > 0 && bRequestBottomLayout){
    mBottomText.setVisibility(View.VISIBLE);
    mHandler.post(new Runnable() {
     
     @Override
     public void run() {
      // TODO Auto-generated method stub
      mBottomText.requestLayout();
      bRequestBottomLayout = false;
     }
    });
   }

   

   if (STATE != 0 || !bRequestBottomLayout) {

    Log.e("spanstring", spanstring);

//    Log.e("", "移除转角span");
    mTopText.getSpannable().removeSpan(mTopText.getSpans()[mTopText.getSpans().length - 1]);

    switch (STATE) {
    case 1:
     mTopText.getSpannable().setSpan(
       mTopText.new LinkClickableSpan(spanstring),
       spanstart,
       lastindex,
       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
     mBottomText.getSpannable().setSpan(
       mBottomText.new LinkClickableSpan(spanstring),
       0,
       spanend - lastindex,
       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
//     Log.e("", "网页");
     break;
    case 2:
     mTopText.getSpannable().setSpan(
       mTopText.new NameClickableSpan(spanstring),
       spanstart,
       lastindex,
       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
     mBottomText.getSpannable().setSpan(
       mBottomText.new NameClickableSpan(spanstring),
       0,
       spanend - lastindex,
       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
//     Log.e("", "人名");
     break;
    case 3:
     mTopText.getSpannable().setSpan(
       mTopText.new TopicClickableSpan(spanstring),
       spanstart,
       lastindex,
       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
     mBottomText.getSpannable().setSpan(
       mBottomText.new TopicClickableSpan(spanstring),
       0,
       spanend - lastindex,
       Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
//     Log.e("", "话题");
     break;
    }
    
    mTopText.setText(mTopText.getSpannable(), BufferType.SPANNABLE);
    mBottomText.setText(mBottomText.getSpannable(), BufferType.SPANNABLE);
   }
   

  }
 
 }

 

说明下难点:

第一,转角处可能存在Span,比如一个话题 #话题话题话题话题话题话题话题话题话题#,可能一半内容在mTopText里面的最后一行显示,而另一半显示不下了,这就需要剪切剩余的String显示到mBottomText里面去,可是Span的内容就会出错,这就需要重新设置下转角处的Span效果了

第二,由于存在图片Span,会导致行高不正确,所以需要在获取到mTopText能显示的最多行数以后,重新判断一次是否正确,如果不正确的话,需要重新调整,不然mTopText会有一部分内容显示不下。


该View实现完成后,在布局中使用include条用,代码中find出来就可以直接使用了


不考虑Span的话,直接将代码中转角处理Span部分删除即可使用,我注释的挺清楚了

下面我们一起来看一个关于SpannableString与SpannableStringBuilder使用例子了,希望这个例子能够对各位同学带来有效帮助的哦。

1、SpannableString、SpannableStringBuilder与String的关系

首先SpannableString、SpannableStringBuilder基本上与String差不多,也是用来存储字符串,但它们俩的特殊就在于有一个SetSpan()函数,能给这些存储的String添加各种格式或者称样式(Span),将原来的String以不同的样式显示出来,比如在原来String上加下划线、加背景色、改变字体颜色、用图片把指定的文字给替换掉,等等。所以,总而言之,SpannableString、SpannableStringBuilder与String一样, 首先也是传字符串,但SpannableString、SpannableStringBuilder可以对这些字符串添加额外的样式信息,但String则不行。

注意:如果这些额外信息能被所用的方式支持,比如将SpannableString传给TextView;也有对这些额外信息不支持的,比如前一章讲到的Canvas绘制文字,对于不支持的情况,SpannableString和SpannableStringBuilder就是退化为String类型,直接显示原来的String字符串,而不会再显示这些附加的额外信息。

2、SpannableString与SpannableStringBuilder区别

它们的区别在于 SpannableString像一个String一样,构造对象的时候传入一个String,之后再无法更改String的内容,也无法拼接多个 SpannableString;而SpannableStringBuilder则更像是StringBuilder,它可以通过其append()方法来拼接多个String;

3、SetSpan()

void setSpan (Object what, int start, int end, int flags)

函数意义:给SpannableString或SpannableStringBuilder特定范围的字符串设定Span样式,可以设置多个(比如同时加上下划线和删除线等),Falg参数标识了当在所标记范围前和标记范围后紧贴着插入新字符时的动作,即是否对新插入的字符应用同样的样式。

参数说明:

object what :对应的各种Span,后面会提到;
int start:开始应用指定Span的位置,索引从0开始
int end:结束应用指定Span的位置,特效并不包括这个位置。比如如果这里数为3(即第4个字符),第4个字符不会有任何特效。从下面的例子也可以看出来。
int flags:取值有如下四个
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE:前后都不包括,即在指定范围的前面和后面插入新字符都不会应用新样式
Spannable.SPAN_EXCLUSIVE_INCLUSIVE :前面不包括,后面包括。即仅在范围字符的后面插入新字符时会应用新样式
Spannable.SPAN_INCLUSIVE_EXCLUSIVE :前面包括,后面不包括。
Spannable.SPAN_INCLUSIVE_INCLUSIVE :前后都包括。

下面写了个小demo,先看一下效果图:

代码如下所示:

 代码如下 复制代码

public class MainActivity extends Activity implements OnClickListener {
    private TextView tv;
    private Button underline_btn;
    private Button strike_btn;
    private Button style_btn;
    private Button font_btn;
    private Button color_btn1;
    private Button color_btn2;
    private Button url_btn;
    private Button image_btn;
    private Button maskfilte_btn;
    private Button Rasterizer_btn;
    private Button spannablestringbuilder;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) this.findViewById(R.id.tv);
        underline_btn = (Button) this.findViewById(R.id.underline_btn);
        strike_btn = (Button) this.findViewById(R.id.strike_btn);
        style_btn = (Button) this.findViewById(R.id.style_btn);
        font_btn = (Button) this.findViewById(R.id.font_btn);
        color_btn1 = (Button) this.findViewById(R.id.color_btn1);
        color_btn2 = (Button) this.findViewById(R.id.color_btn2);
        url_btn = (Button) this.findViewById(R.id.url_btn);
        image_btn = (Button) this.findViewById(R.id.image_btn);
        maskfilte_btn = (Button) this.findViewById(R.id.maskfilte_btn);
        Rasterizer_btn = (Button) this.findViewById(R.id.Rasterizer_btn);
        spannablestringbuilder = (Button) this.findViewById(R.id.spannablestringbuilder);
 
        underline_btn.setOnClickListener(this);
        strike_btn.setOnClickListener(this);
        style_btn.setOnClickListener(this);
        font_btn.setOnClickListener(this);
        color_btn1.setOnClickListener(this);
        color_btn2.setOnClickListener(this);
        url_btn.setOnClickListener(this);
        image_btn.setOnClickListener(this);
        maskfilte_btn.setOnClickListener(this);
        Rasterizer_btn.setOnClickListener(this);
        spannablestringbuilder.setOnClickListener(this);
 
    }
 
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.underline_btn:
            addUnderLineSpan();
            break;
        case R.id.strike_btn:
            addStrikeSpan();
            break;
        case R.id.style_btn:
            addStyleSpan();
            break;
        case R.id.font_btn:
            addFontSpan();
            break;
        case R.id.color_btn1:
            addForeColorSpan();
            break;
        case R.id.color_btn2:
            addBackColorSpan();
            break;
        case R.id.url_btn:
            addUrlSpan();
            break;
        case R.id.image_btn:
            addImageSpan();
            break;
        case R.id.maskfilte_btn:
            addmaskfilteSpan();
            break;
        case R.id.Rasterizer_btn:
            addRasterizerSpan();
            break;
        case R.id.spannablestringbuilder:
            addspannablestringbuilderSpan();
            break;
        }
    }
    /**
     * spannablestringbuilder
     */
    private void addspannablestringbuilderSpan() {
        SpannableStringBuilder ss=new SpannableStringBuilder("红色超链接斜体删除线绿色下划线图片:.");
           //用颜色标记文本
        ss.setSpan(new ForegroundColorSpan(Color.RED), 0, 2,
                //setSpan时需要指定的 flag,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE(前后都不包括).
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        //用超链接标记文本
        ss.setSpan(new URLSpan("tel:4155551212"), 2, 5,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        //用样式标记文本(斜体)
        ss.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 5, 7,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        //用删除线标记文本
        ss.setSpan(new StrikethroughSpan(), 7, 10,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        //用下划线标记文本
        ss.setSpan(new UnderlineSpan(), 10, 16,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        //用颜色标记
        ss.setSpan(new ForegroundColorSpan(Color.GREEN), 10, 12,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        //获取Drawable资源
        Drawable d = getResources().getDrawable(R.drawable.ic_launcher);
        d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
        //创建ImageSpan
        ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BASELINE);
        //用ImageSpan替换文本
        ss.setSpan(span, 18, 19, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.setText(ss);
        tv.setMovementMethod(LinkMovementMethod.getInstance()); //实现文本的滚动
    }
 
    /*
     * Spannable.SPAN_EXCLUSIVE_EXCLUSIVE:前后都不包括,即在指定范围的前面和后面插入新字符都不会应用新样式
     * Spannable.SPAN_EXCLUSIVE_INCLUSIVE :前面不包括,后面包括。即仅在范围字符的后面插入新字符时会应用新样式
     * Spannable.SPAN_INCLUSIVE_EXCLUSIVE :前面包括,后面不包括。
     * Spannable.SPAN_INCLUSIVE_INCLUSIVE :前后都包括。
     */
    /**
     * 光栅效果
     */
    private void addRasterizerSpan() {
        SpannableString spanText = new SpannableString("StrikethroughSpan");
        spanText.setSpan(new StrikethroughSpan(), 0, 7, Spannable.
        SPAN_INCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanText);
        
    }
    /**
     * 修饰效果
     */
    private void addmaskfilteSpan() {
        SpannableString spanText = new SpannableString("benzlocke6666666");
        int length = spanText.length();
        //模糊(BlurMaskFilter)
        MaskFilterSpan maskFilterSpan = new MaskFilterSpan(new BlurMaskFilter(3, Blur.OUTER));
        spanText.setSpan(maskFilterSpan, 0, length - 10, Spannable.
        SPAN_INCLUSIVE_EXCLUSIVE);
        //浮雕(EmbossMaskFilter)
        maskFilterSpan = new MaskFilterSpan(new EmbossMaskFilter(new float[]{1,1,3}, 1.5f, 8, 3));
        spanText.setSpan(maskFilterSpan, length - 10, length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanText);
        
    }
 
    /**
     * 超链接
     */
    private void addUrlSpan() {
        SpannableString spanString = new SpannableString("超链接");
        URLSpan span = new URLSpan("tel:0123456789");
        spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanString);
        
        tv.setMovementMethod(LinkMovementMethod.getInstance());
    }
 
    /**
     * 文字背景颜色
     */
    private void addBackColorSpan() {
        SpannableString spanString = new SpannableString("文字背景颜色");
        BackgroundColorSpan span = new BackgroundColorSpan(Color.YELLOW);
        spanString.setSpan(span, 0, 6, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanString);
    }
 
    /**
     * 文字颜色
     */
    private void addForeColorSpan() {
        SpannableString spanString = new SpannableString("文字颜色");
        ForegroundColorSpan span = new ForegroundColorSpan(Color.BLUE);
        spanString.setSpan(span, 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanString);
    }
 
    /**
     * 字体大小
     */
    private void addFontSpan() {
        SpannableString spanString = new SpannableString("36号字体");
        AbsoluteSizeSpan span = new AbsoluteSizeSpan(36);
        spanString.setSpan(span, 0, 5, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanString);
    }
 
    /**
     * 粗体,斜体
     */
    private void addStyleSpan() {
        SpannableString spanString = new SpannableString("ABCDEF");
        StyleSpan span = new StyleSpan(Typeface.BOLD_ITALIC);
        spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanString);
    }
 
    /**
     * 删除线
     */
    private void addStrikeSpan() {
        SpannableString spanString = new SpannableString("删除线");
        StrikethroughSpan span = new StrikethroughSpan();
        spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanString);
    }
 
    /**
     * 下划线
     */
    private void addUnderLineSpan() {
        SpannableString spanString = new SpannableString("下划线");
        UnderlineSpan span = new UnderlineSpan();
        spanString.setSpan(span, 0, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanString);
    }
 
    /**
     * 图片
     */
    private void addImageSpan() {
        SpannableString spanString = new SpannableString(" ");
        Drawable d = getResources().getDrawable(R.drawable.ic_launcher);
        d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
        ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BASELINE);
        spanString.setSpan(span, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        tv.append("\n");
        tv.append(spanString);
    }
}

[!--infotagslink--]

相关文章

  • Android子控件超出父控件的范围显示出来方法

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • Android模拟器上模拟来电和短信配置

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • 夜神android模拟器设置代理的方法

    夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • Android WebView加载html5页面实例教程

    如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
  • 深入理解Android中View和ViewGroup

    深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深...2016-09-20
  • Android自定义WebView网络视频播放控件例子

    下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
  • Android用MemoryFile文件类读写进行性能优化

    java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
  • Android设置TextView竖着显示实例

    TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02
  • android.os.BinderProxy cannot be cast to com解决办法

    本文章来给大家介绍关于android.os.BinderProxy cannot be cast to com解决办法,希望此文章对各位有帮助呀。 Android在绑定服务的时候出现java.lang.ClassCastExc...2016-09-20
  • Android 实现钉钉自动打卡功能

    这篇文章主要介绍了Android 实现钉钉自动打卡功能的步骤,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下...2021-03-15
  • Android 开发之布局细节对比:RTL模式

    下面我们来看一篇关于Android 开发之布局细节对比:RTL模式 ,希望这篇文章对各位同学会带来帮助,具体的细节如下介绍。 前言 讲真,好久没写博客了,2016都过了一半了,赶紧...2016-10-02
  • C#实现带进度条的ListView

    这篇文章主要介绍了C#实现带进度条的ListView 的相关资料,需要的朋友可以参考下...2020-06-25
  • Android中使用SDcard进行文件的读取方法

    首先如果要在程序中使用sdcard进行存储,我们必须要在AndroidManifset.xml文件进行下面的权限设置: 在AndroidManifest.xml中加入访问SDCard的权限如下: <!--...2016-09-20
  • Android开发之PhoneGap打包及错误解决办法

    下面来给各位简单的介绍一下关于Android开发之PhoneGap打包及错误解决办法,希望碰到此类问题的同学可进入参考一下哦。 在我安装、配置好PhoneGap项目的所有依赖...2016-09-20
  • C#实现3步手动建DataGridView的方法

    这篇文章主要介绍了C#实现3步手动建DataGridView的方法,实例分析了C#实现手动创建DataGridView的原理与技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • JS树形菜单组件Bootstrap TreeView使用方法详解

    这篇文章主要为大家详细介绍了js组件Bootstrap TreeView使用方法,本文一部分针对于bootstrap的treeview的实践,另一部分是介绍自己写的树形菜单,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2017-01-09
  • 用Intel HAXM给Android模拟器Emulator加速

    Android 模拟器 Emulator 速度真心不给力,, 现在我们来介绍使用 Intel HAXM 技术为 Android 模拟器加速,使模拟器运行度与真机比肩。 周末试玩了一下在Eclipse中使...2016-09-20
  • Android判断当前屏幕是全屏还是非全屏

    在安卓开发时我碰到一个问题就是需要实现全屏,但又需要我们来判断出用户是使用了全屏或非全屏了,下面我分别找了两段代码,大家可参考。 先来看一个android屏幕全屏实...2016-09-20