Customize the marquee effect of View

640?wx_fmt=png&wxfrom=5&wx_lazy=1


Technology News Today


According to foreign media reports, the well-known technology media Re/code and online survey platform SurveyMonkey recently conducted a joint survey to select the technology companies that Americans believe have the greatest positive impact on society. The survey asked the question: "Which of the following companies has the greatest impact on society today?" The results showed that topping the list was e-commerce giant Amazon, which received the support of 20% of respondents. It was followed by Google with 15% approval. Apple came in third with just 11 percent of the vote.


About the Author


This article is from  Ding Rui 's contribution, sharing the marquee of Android development custom View, let's take a look! Hope you all like it. 

Ding Rui   's blog address:

https://blog.csdn.net/iamdingruihaha


text


I haven't written anything for a long time. I feel a little wasted and a little rusty. Just recently, there is a demand for a marquee in the project. After the TextView is set up, there are still various conflicts, especially when TextView and EditText coexist, so Simply customize a marquee MarqueeView old rule, first upload the renderings, the gif recording is a bit strange, you can install it to see the actual effect:

640?wx_fmt=gif

The specific features of the control are:

  • There are three scroll modes:

    • Scroll once directly to end

    • After the scroll is completed once, continue the second scroll

    • Scroll until the tail appears, followed by the head

  • The setting of the text content can be of String type or List type

  • The space between text and text can be set

  • The initial position of the text scroll can be set

  • Text size, color, scrolling speed can be set

  • Whether to pause or continue clicking on the text can be set

  • When setting the text content again, whether to initialize the movement position can be set

The effect of this control is not difficult, but it is very suitable for students who are unfamiliar with custom View to practice, especially the vague mathematical calculation and analysis inside, which is quite enjoyable, and friends in need can also save some The time is used directly.

The following is a detailed description of the implementation idea:

custom control properties

According to the control characteristics, it can be easily customized. There is not much explanation here. The properties are as follows:

      <attr name="marqueeview_repet_type" format="enum">
           <enum name="repet_oncetime" value="0"/><!-- 播放一次 -->
           <enum name="repet_interval" value="1"/>  <!--连续播放 循环结束再继续下次循环-->
           <enum name="repet_continuous" value="2"/>  <!--连续播放 到末尾直接继续下次循环-->
       </attr><!--循环模式-->

       <attr name="marqueeview_text_distance" format="integer"/><!--每个item之间的距离-->
       <attr name="marqueeview_text_startlocationdistance" format="float"/><!--开始的起始位置 按距离控件左边的百分比 0~1之间 -->

       <attr name="marqueeview_text_speed" format="float"/><!--播放速度 也就是文字滚动速度-->
       <attr name="marqueeview_text_color" format="color|reference"/><!-- 文字颜色 -->
       <attr name="marqueeview_text_size" format="float"/><!-- 文字大小 -->

       <attr name="marqueeview_isclickalbe_stop" format="boolean"/><!--是否点击暂停-->
       <attr name="marqueeview_is_resetLocation" format="boolean"/><!--重新改变内容的时候 , 是否初始化 位置,默认为true,改变-->

Implement text display and scrolling

首先是文本展示,这个很简单,直接在onDraw方法里面调用 drawText(String text, float x, float y, Paint paint)就可以实现,就可以把文本画出来,不过这里面存在个大大的坑,就是这里面的y坐标getHeight() / 2 + textHeight / 2,文本高度计算如果直接调用rect.height()方法获得的值是比实际高度大的,就导致了文本会偏下一点,准确的获取方法为:

  private float getContentHeight() {

       Paint.FontMetrics fontMetrics = paint.getFontMetrics();
       return Math.abs((fontMetrics.bottom - fontMetrics.top)) / 2;

   }

下面就是让他滚动起来,实现方式是开启死循环,在很短的时间内,改变xLocation的值,再调用 postInvalidate() 方法重新绘制文本就实现了滚动。

   @Override
   public void run() {
       while (isRoll && !TextUtils.isEmpty(content)) {
           try {
               Thread.sleep(10);
               xLocation = xLocation - speed;
               postInvalidate();//每隔10毫秒重绘视图
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }

}

实现三种滚动模式

需求的地基已经实现了,很简单,难的就是添枝加叶了,先来看下MarqueeView的重要属性:

  private String string;//最终绘制的文本
   private float speed = 1;//移动速度
   private int textColor = Color.BLACK;//文字颜色,默认黑色
   private float textSize = 12;//文字颜色,默认黑色
   private int textDistance1= 10;//item间距,dp单位,默认10dp
   private int textdistance ;//textDistance1 转化而来的px宽度
   private String black_count = "";//间距转化成空格距离

   private int repetType = REPET_INTERVAL;//滚动模式
   public static final int REPET_ONCETIME = 0;//一次结束
   public static final int REPET_INTERVAL = 1;//一次结束以后,再继续第二次
   public static final int REPET_CONTINUOUS = 2;//紧接着 滚动第二次

   private float xLocation = 0;//文本的x坐标
   private int contentWidth;//内容的宽度

   private float startLocationDistance = 1.0f;//开始的位置选取,百分比来的,距离左边,0~1,0代表不间距,1的话代表,从右面,1/2代表中间。
   private boolean isClickStop = false;//点击是否暂停
   private boolean isResetLocation = true;//默认为true
   private boolean isRoll = false;//是否继续滚动
   private float oneBlack_width;//空格的宽度

其中repetType是该View的核心围绕点,根据repetType的值,在滚动的时候根据 contentWidth(注意是内容的宽度)与xLocation(滚出屏幕左边的距离)的值的大小来采取相应的措施就可以了。

  • 如果是REPET_ONCETIME //一次结束,此时只需要判断是否contentWidth < (-xLocation),如果是的话,停止死循环就可以了,具体是 stopRoll()方法:

     /**
    * 停止滚动
    */

   public void stopRoll() {
       isRoll = false;
       if (thread != null) {
           thread.interrupt();
           thread = null;
       }

   }
  • 如果是 REPET_INTERVAL //一次结束以后,再继续第二次,同样判断contentWidth <= (-xLocation)如果为true 的话,我们令 xLocation = getWidth(); 就直接实现了需求。

  • 如果是 REPET_CONTINUOUS //首尾相连 滚动第二次,这种方式是比较复杂的, 
    这种模式的滚动,首先我们可以在脑海中想象一下,当内容本身的宽度本来就大于屏幕宽度是什么样的,当内容本身很短的时候,又是什么样子的。如果想象不出,我把两种状态画出来就明白了,刚好直观理解一下contentWidth,他不是指文本总长度,而是指初始化设置的文本的宽度。

640?wx_fmt=jpeg

640?wx_fmt=jpeg

首先我们要明确,REPET_CONTINUOUS模式下在给控件设置文本的时候,就要考虑到控件可以同时容纳几个 文本,计算方法我统一成了 int contentCount = (getWidth() / contentWidth) + 2;也就是当宽度盛不下一个文本的时候,(getWidth() / contentWidth)为0,加2得contentCount为2,我们复制两份去添加。而当宽度盛下三个,不足4个的时候,我们复制五分去添加 ,不过这里要注意,复制的文本之间存在要存在间距,也就是说 此时contentWidth为文本宽度加上间距,而间距是可以设置的,这里我的处理是把宽度量化为” “(空格),也就是转化为几个空格的宽度,然后追加上去,这里处理有点low,不过没想大其他好的办法。设复制完的初始化内容就叫string。

这里把间距转化为空格的个数,首先要计算出空格的宽度,然后把宽度除以空格的宽度,得到空格的个数,然后就能用空格来表示我们要的间距了。这里有个有意思的事情,先看三个字符串:

String s1="   ";
String s2="嗯    ";
String s3="嗯     嗯";

这里用rect去测量以后,其中s1的宽度为0,s2的宽度其实就是一个“嗯”的宽度,而s3得到的是实际宽度。所以就采用了差值去计算的思路:

  /**
    * 计算出一个空格的宽度
    * @return
    */

   private float getBlacktWidth() {

       String text1 = "en en";
       String text2 = "enen";
       return getContentWidth(text1) - getContentWidth(text2);

   }

这样初始化文本就完成了,那么当初始化的滚动到最后了怎么办呢?可以脑中意淫一下,我想到了两种方法去解决: 

首先

640?wx_fmt=jpeg

也就是当移动到左边的距离xLocation 刚好等于contentWidth的时候,我们再把xLocation 重置为0 ,这样就出现了他一直在滚动的障眼法,实现方式是 

if ((-xLocation) == contentWidth){ 
   xLocation=0;
}

不过这种方法只能意淫了,不管xLocation的值重置为0 ,还是speed(滚动速度),还是-speed;都会出现抖动一下的bug,所以想了第二个办法。

640?wx_fmt=jpeg

解释一下,这个方法xLocation一直在负增长,每当xLocation负增长一个contentWidth长度的时候,我们在string 后面在追加一个文本就可以了,实现方式:

 int beAppend = (int) ((-xLocation) / contentWidth);

if (beAppend >= repetCount) {
    repetCount++;
    string = string + content;
}

有同学就疑惑为何不直接用(-xLocation)%contentWidth==0来判断呢,这是为了避免,移动的速度大的时候捕捉不到这个临界点,所以采取了整除然后去比较。

初始化的一些属性

首先是xLocation 的初始值:xLocation = getWidth() * startLocationDistance, 其中 startLocationDistance是我们设置的一个百分比。

设置文本内容的方法用了个重载,当传入List类型的时候,先把List遍历转化成了String

 /**
    * 设置滚动的条目内容  字符串形式的
    *
    * @parambt_control00
    */

   public void setContent(String content2) {
       if (TextUtils.isEmpty(content2)){
           return;
       }
       if (isResetLocation) {//控制重新设置文本内容的时候,是否初始化xLocation。
           xLocation = getWidth() * startLocationDistance;
       }

       if (!content2.endsWith(black_count)) {
           content2 = content2 + black_count;//避免没有后缀
       }
       this.content = content2;

       //这里需要计算宽度啦,当然要根据模式来搞
       if (repetType == REPET_CONTINUOUS) {
//如果说是循环的话,则需要计算 文本的宽度 ,然后再根据屏幕宽度 , 看能一个屏幕能盛得下几个文本

           contentWidth = (int) (getContentWidth(content) + textdistance);//可以理解为一个单元内容的长度

           repetCount = 0;//从0 开始计算重复次数了, 否则到最后 会跨不过这个坎而消失。
           int contentCount = (getWidth() / contentWidth) + 2;
           this.string = "";
           for (int i = 0; i <= contentCount; i++) {
               this.string = this.string + this.content;//根据重复次数去叠加。
           }

       } else {
           if (xLocation < 0 && repetType == REPET_ONCETIME) {
               if (-xLocation > contentWidth) {
                   xLocation = getWidth() * startLocationDistance;
               }
           }
           contentWidth = (int) getContentWidth(content);

           this.string = content2;
       }

       if (!isRoll) {//如果没有在滚动的话,重新开启线程滚动
           continueRoll();
       }


   }

文字的基本属性:

   /**
    * 设置文字大小
    *
    * @param textSize
    */

   public void setTextSize(float textSize) {
       if (textSize > 0) {
           this.textSize = textSize;
           paint.setTextSize(dp2px(textSize));//文字颜色值,可以不设定
           contentWidth = (int) (getContentWidth(content) + textdistance);//大小改变,需要重新计算宽高
       }
   }

这个文字大小设置以后,要注意把间距再contentWidth重新测量获取,要不会导致错乱的。关于文本间距textdistance的设置,还是比较复杂的

/**
    * 设置文字间距  不过如果内容是List形式的,该方法不适用 ,list的数据源,必须在设置setContent之前调用此方法。
    * @param textdistance2
    */

   public void setTextDistance(int textdistance2) {


       //设置之后就需要初始化了

       String black = " ";
       oneBlack_width = getBlacktWidth();//空格的宽度
       textdistance2 = dp2px(textdistance2);
       int count = (int) (textdistance2 / oneBlack_width);//空格个数,有点粗略,有兴趣的朋友可以精细

       if (count == 0) {
           count = 1;
       }

       textdistance = (int) (oneBlack_width * count);
       black_count = "";
       for (int i = 0; i <= count; i++) {
           black_count = black_count + black;//间隔字符串
       }
       setContent(content);
   }

不单单的是个thi.textdistance=textdistance就解决了,因为这个属性设置以后如果我们不去重新把他使用给现有文本,是没有效果的,所以需要重新改变black_count(几个空格组成的字符串),再调用setContent(String string)方法,改变间距。


总结


这样我们的控件基本就完成啦,至于控件的文字颜色、滚动速度等属性就比较简单了,源码已经上传至github,欢迎查看。

https://github.com/385841539/MarqueeView


欢迎长按下图 -> 识别图中二维码

Or  scan and  follow my official account

640.png?

640?wx_fmt=jpeg

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324849644&siteId=291194637