在平时的Android开发中,有时根据项目经理的设计,我们需要实现一些"花里胡哨"的功能,本篇博文就是介绍其中一个很俗气的效果-- 跑马灯

1. 简单实现及问题

1.1 简单实现:

其实在Android开发里,TextView中实现跑马灯非常简单,简单到只需要在xml文件中设置几行属性代码即可,如下:

<TextView
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:textColor="#FF0000"
        android:layout_marginLeft="20dp"
        android:singleLine="true"
        android:text="在平时的Android开发中,版本更新是避免不了的事情(利用跨平台热更新另算),此博文主要介绍一下版本更新时候显示app下载进度以及下载完毕后直接跳转安装界面的操作."
        android:ellipsize="marquee"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:marqueeRepeatLimit="marquee_forever"
        />

其实只要加上四行代码,即可实现:

 android:ellipsize="marquee"
 android:focusable="true"
 android:focusableInTouchMode="true"
 android:marqueeRepeatLimit="marquee_forever" 

是不是非常简单?看下效果:

1.2 出现问题及原因分析:

但是,上面代码里只显示了一个,如果需要加两个甚至更多呢?

测试一下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFFFF"
    android:orientation="vertical"
    tools:context=".activity.HomeActivity">


    <TextView
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:textColor="#FF0000"
        android:layout_marginLeft="20dp"
        android:singleLine="true"
        android:text="在平时的Android开发中,版本更新是避免不了的事情(利用跨平台热更新另算),此博文主要介绍一下版本更新时候显示app下载进度以及下载完毕后直接跳转安装界面的操作."
        android:ellipsize="marquee"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:marqueeRepeatLimit="marquee_forever"
        />

    <TextView
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:textColor="#FF0000"
        android:layout_marginLeft="20dp"
        android:singleLine="true"
        android:text="在平时的Android开发中,版本更新是避免不了的事情(利用跨平台热更新另算),此博文主要介绍一下版本更新时候显示app下载进度以及下载完毕后直接跳转安装界面的操作."
        android:ellipsize="marquee"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:marqueeRepeatLimit="marquee_forever"
        />
</LinearLayout>

只是在xml布局文件中增加一个一模一样的TextView,然后再次运行程序:

哎呀我去,怎么只有一个动了?另外一个没有反应?

其实原理很简单,设置跑马灯效果需要控件获取焦点而且是强制获取,而在第二份代码中,有两个控件强制获取焦点就造成了一个跑另外一个不跑了.

那么如何解决这个问题呢? 答案就是: 自定义控件

1.3 解决方法:

1.3.1 自定义控件(继承于TextView):
import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * Time: 2018/12/28
 * Author: CJ
 * Description:
 **/
public class HomeTextView extends TextView {

    //在代码中使用的时候调用
    public HomeTextView(Context context) {
        this(context, null);
    }

    //在布局文件中使用的时候调用的方法
    //布局文件中的控件最终都会通过反射的形式,转化成代码,在转化的代码中new的时候调用的方法
    //控件的所有属性都会保存到AttributeSet
    public HomeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, -1);
    }

    //在控件的内部让两个参数的构造函数调用的
    public HomeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    // 重写系统的TextView,让其默认获得焦点
    @Override
    public boolean isFocused() {
        return true;
    }
}
1.3.2 修改xml控件布局:
<com.xlgz520.applicationdemo.views.HomeTextView
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:textColor="#FF0000"
        android:layout_marginLeft="20dp"
        android:singleLine="true"
        android:text="在平时的Android开发中,版本更新是避免不了的事情(利用跨平台热更新另算),此博文主要介绍一下版本更新时候显示app下载进度以及下载完毕后直接跳转安装界面的操作."
        android:ellipsize="marquee"
        android:focusable="true"
        android:focusableInTouchMode="true"                         
        android:marqueeRepeatLimit="marquee_forever"/>

    <com.xlgz520.applicationdemo.views.HomeTextView
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:textColor="#FF0000"
        android:layout_marginLeft="20dp"
        android:singleLine="true"
        android:text="在平时的Android开发中,版本更新是避免不了的事情(利用跨平台热更新另算),此博文主要介绍一下版本更新时候显示app下载进度以及下载完毕后直接跳转安装界面的操作."
        android:ellipsize="marquee"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:marqueeRepeatLimit="marquee_forever"/>
1.3.3 效果图:

可以看出,两个都可以动了.

2. 拓展与延伸

在平时开发中,其实控件这块不光在xml中要进行布局,有时候也需要在java文件中进行一些属性设置,而且这种情况在自定义控件其实还是比较常见的,那么试一下在自定义控件中设置属性代码是否有一样的效果呢?

2.1 修改xml代码

<com.xlgz520.applicationdemo.views.HomeTextView
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:textColor="#FF0000"
        android:layout_marginLeft="20dp"
        android:text="在平时的Android开发中,版本更新是避免不了的事情(利用跨平台热更新另算),此博文主要介绍一下版本更新时候显示app下载进度以及下载完毕后直接跳转安装界面的操作."
        />

2.2 修改自定义控件java代码:

在第三个(三个参数那个)的构造方法中,增加如下代码

 //在控件的内部让两个参数的构造函数调用的
    public HomeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        setSingleLine();//使用代码设置单行显示
        setEllipsize(TextUtils.TruncateAt.MARQUEE);//使用代码设置滚动操作
        setFocusableInTouchMode(true);//使用代码设置触摸获取焦点
        setMarqueeRepeatLimit(-1);//设置滚动次数 -1代表无限
    }

2.3 运行效果:

2.4 优点与问题:

  • 优点:

如果项目中有很多地方使用TextView的跑马灯,那么如果按照xml的写法,会有很多的重复代码,因为每个TextView都需要设置上文的四个属性,而如果在自定义构造方法里实现的话,其他地方只需要设置TextView其他的属性即可.

  • 问题:

##### ① 修改xml布局:

<com.xlgz520.applicationdemo.views.HomeTextView
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:textColor="#FF0000"
        android:layout_marginLeft="20dp"
        android:text="在平时的Android开发中,版本更新是避免不了的事情(利用跨平台热更新另算),此博文主要介绍一下版本更新时候显示app下载进度以及下载完毕后直接跳转安装界面的操作."
        />

    <EditText
        android:layout_marginTop="15dp"
        android:layout_marginLeft="20dp"
        android:layout_width="300dp"
        android:layout_height="wrap_content" />

只是在自定义TextView下方增加了一个EditText,然后我们运行一下程序:

看出问题了么?

当我点击EditText的时候,跑马灯停止了,那么如何解决呢?

##### ② 解决方法:

修改自定义TextView的代码,重写父类的一个方法:

//焦点切换调用的方法
    //focused : 焦点是否释放
    //direction : 焦点移动的方向
    //previouslyFocusedRect : 焦点从哪个控件过来
    @Override
    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
        //当焦点被抢夺的时候,不能抢夺textview的焦点
        //如果焦点没有被抢夺,调用系统的方法,帮我们保留焦点
        //如果焦点被抢夺了,禁止调用系统的方法,禁止系统移除焦点
        if (focused) {
            super.onFocusChanged(focused, direction, previouslyFocusedRect);
        }
    }

再次查看一下效果: