TextSwticher 与 TextView 实现上下滚动和跑马灯效果

作者:郑少锐 ,欢迎转载,也请保留这份申明,谢谢。

请保留这份申明:http://blog.csdn.net/u011418943/article/details/51871482

太久没写博客了,主要是想写一些有质量的,今天碰到这个需求觉得挺有意思的,即像标题写的那样,这里记录一下,废话不多说,先上一个效果图:
单行显示:
TextSwticher 与 TextView 实现上下滚动和跑马灯效果_第1张图片
大于一行显示:
TextSwticher 与 TextView 实现上下滚动和跑马灯效果_第2张图片
TextSwticher 与 TextView 实现上下滚动和跑马灯效果_第3张图片

即,单行上下滚动,大于一行则实现跑马灯。
这里有两个难点:
· 如何判断字幕是超过了一行,这个受字体大小和字节编码影响的
· 如何根据超过了多少定制不同的滚动时间
首先还是先实现上下滚动的效果,从最基础的步骤来

1、TextSwitcher 上下滚动

我们需要上下滚动的效果,所以需要重新 TextSwitcher 的构造方法,这个跟ImageSwitcher 一个样,差别在 ImageSwitcher 的 makeview 返回的是Imageview,而 TextSwitcher 的makeview 返回的 TextView 。首先,先实现上下滚动:

public class TextSwitcherView extends TextSwitcher implements ViewFactory {
    private ArrayList reArrayList = new ArrayList();
    private int resIndex = 0;
    private final int UPDATE_TEXTSWITCHER = 1;
    private int timerStartAgainCount = 0;
    private Context mContext;
    public TextSwitcherView(Context context) {

        super(context);
        // TODO Auto-generated constructor stub
        mContext = context;
        init();
    }
    public TextSwitcherView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        init();
        // TODO Auto-generated constructor stub
    }

    private void init(){
        this.setFactory(this);
        this.setInAnimation(getContext(),R.anim.vertical_in);
        this.setOutAnimation(getContext(), R.anim.vertical_out);
        Timer timer = new Timer();
        timer.schedule(timerTask, 1,3000);
    }
    TimerTask timerTask =  new TimerTask() {

        @Override
        public void run() {   //不能在这里创建任何UI的更新,toast也不行
            // TODO Auto-generated method stub
                Message msg = new Message();
                msg.what = UPDATE_TEXTSWITCHER;
                handler.sendMessage(msg);
        }
    };
    Handler handler = new Handler(){
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case UPDATE_TEXTSWITCHER:
                updateTextSwitcher();

                break;
            default:
                break;
            }

        };
    };
    /**
     * 需要传递的资源
     * @param reArrayList
     */
    public void getResource(ArrayList reArrayList) {
        this.reArrayList = reArrayList;
    }
    public void updateTextSwitcher() {
        if (this.reArrayList != null && this.reArrayList.size()>0) {
            this.setText(this.reArrayList.get(resIndex++));
            if (resIndex > this.reArrayList.size()-1) {
                resIndex = 0;
            }
        }

    }
    @Override
    public View makeView() {
        // TODO Auto-generated method stub  
        TextView tView = new TextView(getContext());
        tView.setTextSize(20);
        tView.setTextColor(0xff24aaff);
        return tView;
    }
}

数据的获取是通过一个 ArrayList 来获取的,然后用一个定时器Timer实现上下滚动,3s执行一次。现在的 makeview 返回的是一个TextView ,所以现在只是上下滚动。
上下滚动动画代码如下:
vertical_in.xml:


<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:duration="1000"
        android:fromYDelta="100%p"
        android:toYDelta="0%p" />
set>

vertical_out.xml:


<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:duration="1000"
        android:fromYDelta="0%p"
        android:toYDelta="-100%p" />
set>

2、TextView 添加跑马灯效果

我们知道,跑马灯的效果是focusable = true,即要焦点放在它身上,所以,这里我们继承 TextView,重新 isFocus 方法即可,即可实现跑马灯的效果,如下:

public class WaterTextView extends TextView {
    public WaterTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
    }
    public WaterTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }
    public WaterTextView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }
    @Override
    @ExportedProperty(category = "focus")
    public boolean isFocused() {  
        // TODO Auto-generated method stub
        return true;
    }
}

既然要实现跑马灯,所以,要在 makeView 返回中,修改返回的是我们的重新的TextView 即可。如:

public View makeView() {
        // TODO Auto-generated method stub


        WaterTextView tView = new WaterTextView(getContext());
        tView.setSingleLine(true);
        tView.setEllipsize(TruncateAt.MARQUEE);
        tView.setPadding(getResources().getDimensionPixelOffset(R.dimen.x10),
                getResources().getDimensionPixelOffset(R.dimen.x5), 
                getResources().getDimensionPixelOffset(R.dimen.x10), 
                getResources().getDimensionPixelOffset(R.dimen.x5));
        tView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimensionPixelOffset(R.dimen.x28));
        tView.setMarqueeRepeatLimit(1);
        Log.d("zsr", "more line");

        return tView;
    }

这里设置了一些跑马灯的属性,注意字体那里,有两个参数,第一个传入的是像素的标志位,因为 setTextSize 的默认参数为 sp,如果后面传入的是像素,是要换算的,所以这里,我们统一用像素,如果你对自适应不了解的话,可以看我以前写的博客:
http://blog.csdn.net/u011418943/article/details/51247828
你现在可以传入一些比较长的参数,看看是不是当字数超过一行地时候,有流水灯的效果。

3、如何判断超过了一行

在这里纠结了很多时间,刚开始的时候考虑像素什么问题,但获取的数据都不对,这时我的想法是这样的,既然规定了字体大小,那么字体的字节大小就可以确定的,再根据计算一个字节占了屏幕多少,就可以计算一个一行占了多少字节了。
虽然这种方法会存在一些问题,但目前我在公司的板子上都没出现过。获取字节,中文一般占两个字节,英语占一个字节。
下面是我的获取字节的方法:

public  void subStr(String str, int subSLength)  
            throws UnsupportedEncodingException{ 

        char[] array = str.toCharArray();  //获取字节

        Log.d("zsr", "strbyte/arraybyte: "+str.toString().getBytes("GBK").length+"  "+array.length);
        if (str.toString().getBytes("GBK").length > subSLength) {
            int shi = str.toString().getBytes("GBK").length/subSLength;
            int ge = str.toString().getBytes("GBK").length%subSLength;
            if (shi > 0 && ge != 0) {  //不小于一行,分开显示
                 for (int i = 0; i < array.length; i++) {
                        if((char)(byte)array[i]!=array[i]){
                            byteCount += 2;  //如果是中文,则自加2
                            stringCount += 1;
                        }else{
                            byteCount += 1; //如果不是中文,则自加1
                            stringCount += 1;
                        }
                        if (byteCount >= subSLength) {
                            getRealCount = stringCount;
                            byteCount = 0;
                            stringCount = 0;
                        }
                  }
            }else {
                subArrayList.add(str); //小于一行则正常显示
                Log.d("zsr", "cannot read?");
             }

    }

我的一行是 48 ,即subSLength = 48 ,既然我们已经成功截取了一行,这里就有两种方法了。
· 不采用跑马灯,采用分行,即截断分行一行一行显示
· 继续采用跑马灯,等检测到大于一行,让上下滚动效果消失,滚动完再上下滚动

第一种: 在上面的基础上修改:

public  void subStr(String str, int subSLength)  
            throws UnsupportedEncodingException{ 

        char[] array = str.toCharArray();  //获取字节

        Log.d("zsr", "strbyte/arraybyte: "+str.toString().getBytes("GBK").length+"  "+array.length);
        if (str.toString().getBytes("GBK").length > subSLength) {
            int shi = str.toString().getBytes("GBK").length/subSLength;
            int ge = str.toString().getBytes("GBK").length%subSLength;
            if (shi > 0 && ge != 0) {  //不小于一行,分开显示
                 for (int i = 0; i < array.length; i++) {
                        if((char)(byte)array[i]!=array[i]){
                            byteCount += 2;  //如果是中文,则自加2
                            stringCount += 1;
                        }else{
                            byteCount += 1; //如果不是中文,则自加1
                            stringCount += 1;
                        }
                        if (byteCount >= 48) {
                            getRealCount = stringCount;
                            byteCount = 0;
                            stringCount = 0;
                        }
                  }
                Log.d("zsr_count", "waterloopTime: "+waterloopTime);
                 for (int i = 0; i <= shi; i++) {  //把超过一行地数据存到一个数组
                     if (i == shi) {
                         subArrayList.add(str.substring(getRealCount*i,str.length()));
                     }else {
                        subArrayList.add(str.substring(getRealCount*i,getRealCount*(i+1)));
                     }
                 }
              }
            }else {
                subArrayList.add(str); //小于一行则正常显示
                Log.d("zsr", "cannot read?");


             }

    }

这里的思路是,把多行的截取成一段段存到一个数组里,注意这里的字节和数组的元素是不一样的,中文英语都是一个,所以,我才在上面写上StringCount。那么更新这里,就写上:

/**
     * 需要传递的资源
     * @param reArrayList
     */
    public void setArrayListData(ArrayList reArrayList) {
        this.reArrayList = reArrayList;
        this.dataflag = 1;

    }

    public void setDefaultData(String string) {
        this.string = string;
        this.dataflag = 0;
    }
数据处理:
public void updateTextSwitcher() {
        if (dataflag == 1) {  //如果有数据,显示数据
            if (this.reArrayList != null && this.reArrayList.size()>0) {
                try {
                    //this.setText(subStr(reArrayList.get(resIndex++),48));
                    if (!subArrayFlag) {
                        subStr(reArrayList.get(resIndex++), 48);

                        if (subArrayList != null && subArrayList.size()>0) {
                            Log.d("zsr", "size: "+subArrayList.size());
                            if (subArrayList.size() == 1) {  //单行
                                this.setText(subArrayList.get(subIndex));
                                subArrayList.clear();
                            }else if(subArrayList.size() > 1) {
                                this.setText(subArrayList.get(subIndex));
                                subArrayFlag = true;
                            }

                        }

                    }else {
                        subIndex++;
                        if (subArrayList != null && subArrayList.size()>0) {
                            if (subIndex == subArrayList.size()-1) {
                                this.setText(subArrayList.get(subIndex));
                                subArrayFlag = false;
                                subArrayList.clear();
                                subIndex = 0;
                            }

                        }

                    }




                } catch (UnsupportedEncodingException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
                if (resIndex >=reArrayList.size()) {
                    resIndex = 0;
                }   

            }
        }else { //没有
            this.setText(this.string);
        }


    }

第二种,实现跑马灯:

怎么样取消上下滚动,让左右滚动呢?因为我们上面的是一个定时器Timer的,所以,只要改变参数就行了,为了方便大家看,我们重新写一个更新的方法,如:

private void updateText(){
        if (this.dataflag == 1) {
            if (this.reArrayList != null && this.reArrayList.size()>0) {
                    try {
                        subStr(reArrayList.get(resIndex), 48);
                        Log.d("zsr", "size: "+subArrayList.size());
                        if (subArrayList != null && subArrayList.size()>0) {
                            if (subArrayList.size() == 1){  // 单行
                                waterTextStatus = false;
                                this.setText(this.reArrayList.get(resIndex));
                                subArrayList.clear();
                                Log.d("zsr", "ONE");



                            }else if(subArrayList.size()>1){ // 多行
                                waterTextStatus = true;
                                this.setText(this.reArrayList.get(resIndex));
                                Log.d("zsr", "MORE");
                                subArrayList.clear();

                            }
                        }
                        resIndex++;
                        if (resIndex >= reArrayList.size()-1) {
                            resIndex = 0;
                        }
                    } catch (UnsupportedEncodingException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                }
        }else {
            this.setText(this.string);
        }
    }

其实跟上面没啥区别。注意!这里,我用了waterTextStatus 来区别是不是大于一行。然后接着就是改变变量了

class MyTimerTask extends TimerTask {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            if (!waterTextStatus) {
                msg = new Message();
                msg.what = UPDATE_TEXTSWITCHER;
                handler.sendMessage(msg);
            }else { //多行
                startAgainCount ++;
                if (startAgainCount > waterloopTime) {
                    startAgainCount = 0;
                    waterTextStatus = false;
                }
            }

        }
    };

线程的更新也很简单:

Handler handler = new Handler(){
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case UPDATE_TEXTSWITCHER:
                //updateTextSwitcher();
                //index = next();
                updateText();
                break;
            default:
                break;
            }

        };
    };

可以看到,如果多行,就不然它去刷新线程的UI更新,等它跑完了再去更新,至于怎么判断是否跑完呢?这里我也不知道怎么弄,这能用笨方法,就是延时,尴尬尴尬:

public  void subStr(String str, int subSLength)  
            throws UnsupportedEncodingException{ 

        char[] array = str.toCharArray();  //获取字节

        Log.d("zsr", "strbyte/arraybyte: "+str.toString().getBytes("GBK").length+"  "+array.length);
        if (str.toString().getBytes("GBK").length > subSLength) {
            int shi = str.toString().getBytes("GBK").length/subSLength;
            int ge = str.toString().getBytes("GBK").length%subSLength;
            if (shi > 0 && ge != 0) {  //不小于一行,分开显示
                 for (int i = 0; i < array.length; i++) {
                        if((char)(byte)array[i]!=array[i]){
                            byteCount += 2;  //如果是中文,则自加2
                            stringCount += 1;
                        }else{
                            byteCount += 1; //如果不是中文,则自加1
                            stringCount += 1;
                        }
                        if (byteCount >= 48) {
                            getRealCount = stringCount;
                            byteCount = 0;
                            stringCount = 0;
                        }
                  }
                 Log.d("zsr_count", "shi/ge: "+shi+"  "+ge);
                if (ge>0 && ge<=7) {
                    waterloopTime = 3*shi; 
                }else if (ge>7 && ge<=16) {
                    waterloopTime = 3*shi+1; 
                }else if(ge>16 && ge<=25){
                    waterloopTime = 4*shi+1; 
                }else if(ge>25 && ge<=35){
                    waterloopTime = 5*shi+1; 
                }else {
                    waterloopTime = 6*shi+2; 
                }
                Log.d("zsr_count", "waterloopTime: "+waterloopTime);
                 for (int i = 0; i <= shi; i++) {
                     if (i == shi) {
                         subArrayList.add(str.substring(getRealCount*i,str.length()));
                     }else {
                        subArrayList.add(str.substring(getRealCount*i,getRealCount*(i+1)));
                     }
                 }
              }
            }else {
                subArrayList.add(str); //小于一行则正常显示
                Log.d("zsr", "cannot read?");
             }

    }

可以看到,这里的waterloopTime 是根据截取的行数和个数来计算的,当然,我现在心里一直有点忐忑,延时能不用就不要用的,如果你有好建议,也请告诉,谢谢。

布局很简单:

<com.vst.launchertext.TextSwitcherView
            android:id="@+id/watertext"
            android:layout_width="match_parent"
            android:layout_height="match_parent"

            >
        com.vst.launchertext.TextSwitcherView>

对了,像这些标志位的申明,我们需要用volatile 关键字修饰,目的是让它能及时通过线程更新。

算法已解决,欢迎查看我的这篇文章:
http://blog.csdn.net/u011418943/article/details/51889962

你可能感兴趣的:(Android-进阶)