Android UI 与文本相关的控件

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/CodingEnding/article/details/79478477

前言

谈到Android中与文本相关的控件,容易想到的自然是TextView和EditText。这两类控件的使用率很高,基本使用方法也很简单。但是如果追寻细节,会发现它们还隐藏着很多的黑科技知识。本文的内容包括:TextView、EditText、AutoCompleteTextView、MultiAutoCompleteTextView。

TextView

相关属性

TextView是很常用的控件,下文主要介绍一下有用的属性:

  1. android:textAllCaps:是否自动将小写字母转化为大写字母。
  2. android:textStyle:字体风格,多个属性用|分割。可选值为[bold|italic|normal],对应[加粗、斜体、正常]
  3. android:maxLines:最大行数(多余的部分将被省略)
  4. android:minLines:最小行数(缺少的部分由空白填充)
  5. android:ellipsize:设置省略符号...出现的位置。可选值为[none|start|end|middle|marquee],对应[无省略符、开头、结尾、中间、跑马灯模式]。当选择marquee时需要配合singleLine使用。
  6. android:drawableTop/android:drawableBottom/…:这一系列方法用于在文字周围设置图片。
  7. android:drawablePadding:设置图片和文字的间距(需要配合上述drawableXXX系列方法共同使用)。
  8. android:autoLink:设置需要自动识别的链接格式。可选值为[none|all|email|phone|web|map],对应[不识别、全部识别、邮箱、电话号码、网址、地图]
  9. android:textColorLink:设置链接颜色。
  10. android:lineSpacingExtra:设置行间距。
  11. android:lineSpacingMultiplier:设置行间距的倍数。 [浮点型]
  12. android:textIsSelectable:文本内容是否可以选中。
  13. android:textColorHighlight:文本被选中后高亮显示的颜色。
  14. android:textScaleX:文本的横向拉伸倍数。 [浮点型]

支持HTML格式的字符串

对于HTML格式的字符串,需要先使用Html的静态方法fromHtml将字符串转换为Spanned对象,再将该对象设置给TextView,示例代码如下:

String htmlStr="这是一段测试文字。用于测试TextView对HTML的支持。如下:<a href='https://www.baidu.com'>百度一下</a>";
Spanned htmlSpanned=Html.fromHtml(htmlStr);
htmlStrTextView.setText(htmlSpanned);

注意,如果想让文本中的超链接生效(点击打开浏览器),还需要为TextView设置LinkMovementMethod,如下:

htmlStrTextView.setMovementMethod(new LinkMovementMethod());

Spannable的应用

在某些情况下,一段文本的不同区域可能要采取不同的样式,那么就需要Spannable的帮助。关于Spannable的使用,主要分为三步:

  1. 创建一个Spannable对象(通过SpannableString或SpannableStringBuilder)
  2. 通过setSpan方法为Spannable对象设置Span(可以设置多次)
  3. 为TextView设置Spannable

setSpan的方法原型如下:

//what:需要设置的span(这一系列类都以Span结尾)
//start:span作用范围的开始位置
//end:span作用范围的结束位置
//flags:标识作用范围是否包含start和end所在的位置(一般选择Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
public void setSpan(Object what, int start, int end, int flags);

示例代码:

Spannable testSpannable=new SpannableString("这是一段测试文字。用于测试TextView对Spannable的支持。包括背景色、前景色、下划线区域、链接、点击事件等");
testSpannable.setSpan(new BackgroundColorSpan(Color.BLUE),
        37,40,Spanned.SPAN_INCLUSIVE_EXCLUSIVE);//设置背景色
testSpannable.setSpan(new ForegroundColorSpan(Color.RED),
        41,44,Spanned.SPAN_INCLUSIVE_EXCLUSIVE);//设置前景色
testSpannable.setSpan(new UnderlineSpan(),
        45,50,Spanned.SPAN_INCLUSIVE_EXCLUSIVE);//设置下划线
testSpannable.setSpan(new URLSpan("http://blog.csdn.net/codingending"),
        51,53,Spanned.SPAN_INCLUSIVE_EXCLUSIVE);//设置超链接
testSpannable.setSpan(new ClickableSpan() {
    @Override
    public void onClick(View widget) {
        Toast.makeText(widget.getContext(),"文本内点击",Toast.LENGTH_SHORT).show();
    }
},54,58,Spanned.SPAN_INCLUSIVE_EXCLUSIVE);//设置点击事件
spannableTextView.setText(testSpannable);
spannableTextView.setMovementMethod(new LinkMovementMethod());//支持超链接和点击事件

效果截图:

可以使用的span都在android.text.style下,下面列出一些常见的span:

  1. AbsoluteSizeSpan:设置字符的绝对尺寸
  2. BackgroundColorSpan:设置背景色
  3. ForegroundColorSpan:设置文字颜色
  4. StrikethroughSpan:设置删除线样式
  5. UnderlineSpan:设置下划线样式
  6. URLSpan:设置链接样式(配合LinkMovementMethod)
  7. ClickableSpan:设置可点击样式(配合LinkMovementMethod)
  8. StyleSpan:设置粗体、斜体等样式
  9. SubscriptSpan:设置下标样式
  10. SuperscriptSpan:设置上标样式
  11. BulletSpan:无序列表的小圆点样式
  12. ImageSpan:在文本中插入图片
  13. LeadingMarginSpan:设置文本缩进
  14. MaskFilterSpan:设置滤镜样式(模糊、浮雕效果)
  15. QuoteSpan:设置引用样式
  16. ScaleXSpan:设置横向缩放样式

EditText

相关属性

EditText是TextView的子类,所以TextView有的属性EditText同样也有,这里仅仅介绍一些上文没有提到的属性:

  1. android:hint:内容为空时的提示文字。
  2. android:textColorHint:提示文字的颜色。
  3. android:inputType:限制输入内容的格式。可选值很多,下文将详细讲解。
  4. android:maxLength:限制文本的最大长度,超出部分将不会显示。
  5. android:digits:限制能够输入的字符。
  6. android:textCursorDrawable:设置光标样式。
  7. android:imeOptions:设置虚拟键盘右下角的Enter键行为,下文将详细讲解。

自定义样式

在本例中,将演示如何通过改变EditText的默认样式,最终的效果如下:

首先,准备一张图片作为EditText的drawableLeft资源,并放入对应的drawable文件夹下。在本例中命名为ic_account_box.png

然后,在drawable文件下新建两个shape资源,分别作为EditText在获得焦点和失去焦点时的下划线。在本例中命名为link_pink.xmllink_blue.xml

link_pink.xml:

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <size android:height="2dp" android:width="500dp"/>
    <solid android:color="#FF4081"/>
</shape>

link_blue.xml:

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <size android:height="2dp" android:width="500dp"/>
    <solid android:color="#3F51B5"/>
</shape>

接着,在drawable文件夹下新建一个selector资源,作为EditText的drawableBottom资源,在本例中命名为edit_text_bottom_drawable.xml

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 获得焦点时 -->
    <item android:state_focused="true"
        android:drawable="@drawable/line_pink"/>
    <!-- 未获得焦点时 -->
    <item android:state_focused="false"
        android:drawable="@drawable/line_blue"/>
    <!-- 默认状态 -->
    <item android:state_focused="true"
        android:drawable="@drawable/line_blue"/>
</selector>

最后,将EditText的background设置为null,应用各个drawable资源并通过drawablePadding属性设置图片和文字的间距,代码如下:

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@null"
    android:drawableLeft="@drawable/ic_account_box"
    android:drawableBottom="@drawable/edit_text_bottom_drawable"
    android:drawablePadding="2dp"
    android:hint="自定义样式的EditText" />

监听用户输入

通过为EditText设置监听器,可以在文本内容发生改变时触发相应的回调方法。

//监听用户的输入
watchWriteEditText.addTextChangedListener(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        Log.i(TAG,"beforeTextChanged:"+s);//内容改变前
    }
    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        Log.i(TAG,"onTextChanged:"+s);//内容改变时
    }
    @Override
    public void afterTextChanged(Editable s) {
        Log.i(TAG,"afterTextChanged:"+s);//内容改变后
    }
});

限制输入内容

1.使用android:inputType属性指定输入内容的格式:

通过为EditText设置android:inputType属性,可以指定输入内容的格式,如邮箱、数字、密码等。同时,输入法也会呈现最合适的输入界面。这个属性的可选值有很多,下面列出其中一部分:

  1. number:数字格式
  2. phone:电话号码格式
  3. text:文本格式
  4. date:日期格式
  5. time:时间格式
  6. datetime:时间日期格式
  7. numberDecimal:带小数的数字格式
  8. numberSigned:带符号的数字格式
  9. numberPassword:数字密码格式
  10. textEmailAddress:邮箱地址格式(带有@符号)
  11. textEmailSubject:邮件主题格式
  12. textPersonName:人名格式
  13. textPassword:密码格式
  14. textVisiblePassword:可见的密码格式
  15. textMultiLine:多行输入格式(允许用户输入包含换行符的长字符串)
  16. textUri:链接格式(带有/符号)
  17. textFilter:文本筛选格式(比如用于筛选一个列表中的内容)
  18. textShortMessage:短消息格式(比如一条短信)
  19. textLongMessage:长消息格式(比如邮件的正文)
  20. textWebEditText:网页表单中的文本格式
  21. textWebEmailAddress:网页表单中的邮箱地址格式
  22. textWebPassword:网页表单中的密码格式

2.使用android:digits属性指定能够输入的字符:

如果想要限定EditText能够输入的字符,只需要设置android:digits属性即可,示例代码如下:

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:digits="0123456789*#"
    android:hint="只能输入0123456789*#" />

3.使用InputFilter限制输入内容

如果需要实现的内容限制规则比较复杂,就需要代码的配合了。简单来说,要定义自己的InterFilter子类并重写filter方法。示例代码如下:

//通过代码限制输入内容只能为数字
InputFilter filter=new InputFilter() {
    @Override
    public CharSequence filter(CharSequence source, int start, int end,
                               Spanned dest, int dstart, int dend) {
        if(!TextUtils.isDigitsOnly(source)){
            return "";//如果当前输入的内容包含数字之外的字符,就默认输入为空字符
        }
        return source;
    }
};
codeLimitInputEditText.setFilters(new InputFilter[]{filter});//可以设置多个InputFilter

setFilters方法的参数是一个数组,因此我们可以为同一个EditText设置多个InputFilter。

filter方法的参数含义如下:

  • source:当前输入的内容(如果执行了删除则为null)
  • start:输入内容的开始位置
  • end:输入内容的结束位置
  • dest:当前显示的内容(准确的说是执行输入操作前的内容)
  • dstart:当前内容的开始位置
  • dend:当前内容的结束位置

自定义回车键样式及行为

在虚拟键盘的右下角有一个默认的回车键按钮,通过设置EditText的imeOptions属性,可以自定义这个按钮。

imeOptions的可选值有多个,下面列出常见的几种及其含义:

  1. actionSearch:搜索
  2. actionNext:下一步
  3. actionPrevious:上一步
  4. actionDone:编辑已完成(收起虚拟键盘)
  5. actionGo:跳转
  6. actionNone:默认样式(回车键)
  7. actionUnspecified:默认样式(回车键)
  8. actionSend:发送

首先,为EditText设置android:inputTypeandroid:imeOptions属性,这两者配合才会生效。示例代码如下:

<EditText
    android:id="@+id/edit_text_style_ime_option"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:inputType="text"
    android:imeOptions="actionGo"
    android:hint="自定义右下角回车键" />

然后,在代码中为EditText设置OnEditorActionListener,通过判断actionId为自定义回车键指定相应的动作。示例代码如下:

//自定义虚拟键盘右下角的行为
styleOmeOptionEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
    @Override
    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
        if(actionId== EditorInfo.IME_ACTION_SEARCH){
            Toast.makeText(v.getContext(),"点击了搜索按钮",Toast.LENGTH_SHORT).show();
            return true;
        }
        return false;
    }
});

最终的演示效果截图:

AutoCompleteTextView

AutoCompleteTextView是具有输入提示功能的输入控件,属于EditText的直接子类。

相关属性

与EditText相关的属性AutoCompleteTextView同样也有,因此这里仅仅列出一些其特有的属性:

  1. android:completionThreshold:设置会弹出输入提示的最小内容长度。
  2. android:dropDownHorizontalOffset:下拉列表的水平偏移量。
  3. android:dropDownVerticalOffset:下拉列表的垂直偏移量
  4. android:dropDownWidth:下拉列表的宽度。

基本使用

XML代码:

<AutoCompleteTextView
    android:id="@+id/auto_complete_text_view_normal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:completionThreshold="1"
    android:hint="请输入关键词" />

设置Adapter:

String[] dataResArray={"coding","ending","codingending","coding and ending"};
ArrayAdapter<String> normalAdapter=new ArrayAdapter<String>(getActivity(),
        android.R.layout.simple_list_item_1,dataResArray);
normalAutoCompleteView.setAdapter(normalAdapter);

效果截图:

自定义过滤条件和下拉列表样式

某些情况下,需要展示复杂的数据(比如自定义的对象),使用系统的ArrayAdapter并不能满足需求。我们就需要自定义Adapter,并设置相应的过滤逻辑。大致步骤如下:

  1. 首先,我们应该自定义BaseAdapter的子类(本例中为BookAdapter),并实现相应的方法(getCountgetItemgetItemIdgetView),以实现自定义的下拉列表样式。
  2. 然后,让BookAdapter实现Filterable接口,并实现getFilter方法,保证可以对列表内容进行过滤。
  3. 由于getFilter方法需要返回一个Filter对象(过滤器),我们还需要定义一个Filter的子类(在本例中为BookFilter),并实现performFilteringpublishResults方法。为了方便操作,可以将BookFilter定义为BookAdapter的内部类。
  4. 最后,为AutoCompleteTextView设置自定义的适配器对象即可。

实体类:

public class Book {
    private String name;
    private int imageRes;//图片资源

    public Book(String name, int imageRes) {
        this.name = name;
        this.imageRes = imageRes;
    }

    @Override
    public String toString() {//重写toString方法,保证选择下拉提示列表中的某一项时返回书名
        return name;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getImageRes() {
        return imageRes;
    }
    public void setImageRes(int imageRes) {
        this.imageRes = imageRes;
    }
}

自定义Adapter和Filter:

public class StyleFilterAdapter extends BaseAdapter implements Filterable{
    private Context context;
    private List<Book> dataList;//数据源
    private List<Book> originDataList;//保存原始数据
    private BookFilter bookFilter;//过滤器
    private final Object lock=new Object();//同步锁(为了在进行数据复制时保证线程安全)

    public StyleFilterAdapter(Context context, List<Book> dataList) {
        this.context = context;
        this.dataList = dataList;
    }
    @Override
    public int getCount() {
        return dataList.size();
    }
    @Override
    public Object getItem(int position) {
        return dataList.get(position);
    }
    @Override
    public long getItemId(int position) {
        return position;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder=null;
        if(convertView==null){
            LayoutInflater inflater=LayoutInflater.from(context);
            convertView=inflater.inflate(R.layout.auto_complete_view_item,parent,false);
            holder=new ViewHolder();
            holder.bookImage=convertView.findViewById(R.id.book_image);
            holder.bookNameView=convertView.findViewById(R.id.book_name);
            convertView.setTag(holder);
        }else{
            holder= (ViewHolder) convertView.getTag();
        }
        Book book=dataList.get(position);
        holder.bookImage.setImageResource(book.getImageRes());
        holder.bookNameView.setText(book.getName());

        return convertView;
    }

    static class ViewHolder{//View复用机制
        ImageView bookImage;
        TextView bookNameView;
    }

    @Override
    public Filter getFilter() {
        if(bookFilter==null){
            bookFilter=new BookFilter();
        }
        return bookFilter;
    }

    //用于筛选Book列表的过滤器
    class BookFilter extends Filter{
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {//指定过滤逻辑
            FilterResults results=new FilterResults();

            if(originDataList==null){//为原始数据列表赋值
                synchronized (lock){//添加同步锁
                    originDataList=new ArrayList<>(dataList);
                }
            }

            if(TextUtils.isEmpty(constraint)){//如果筛选条件为空直接返回原始数据列表的副本
                synchronized (lock){//添加同步锁
                    List<Book> tempList=new ArrayList<>(originDataList);
                    results.values=tempList;
                    results.count=tempList.size();
                }
            }else{
                List<Book> tempList=new ArrayList<>(originDataList.size());//保存已筛选的数据
                for(Book book:originDataList){
                    if(book.getName().contains(constraint)){//保存包含关键字的数据
                        tempList.add(book);
                    }
                }
                results.values=tempList;
                results.count=tempList.size();
            }
            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint,
                                      FilterResults results) {//将过滤结果反馈给Adapter
            dataList= (List<Book>) results.values;
            if(results.count>0){
                notifyDataSetChanged();//刷新数据
            }else{
                notifyDataSetInvalidated();//重绘控件
            }
        }
    }
}

在代码中使用:

final List<Book> bookList=new ArrayList<>();
bookList.add(new Book("《小王子》",R.mipmap.ic_launcher));
bookList.add(new Book("《狮子王》",R.mipmap.ic_launcher));
bookList.add(new Book("《王小波全集》",R.mipmap.ic_launcher));
final StyleFilterAdapter styleAdapter=new StyleFilterAdapter(getActivity(),bookList);
styleAutoCompleteView.setAdapter(styleAdapter);

在输入内容为空时弹出下拉列表

默认情况下,即使将android:completionThreshold属性设置为0,AutoCompleteTextView也不会在已输入内容为空时弹出下拉提示列表。下面介绍一种简单的解决方案:

styleAutoCompleteView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        if(hasFocus){//保证在没有输入内容的情况下(但是有焦点)也能获得自动补全的提示
            styleAutoCompleteView.showDropDown();//展示下拉列表(会根据内容自动筛选)
        }
    }
});

逻辑很简单,就是在AutoCompleteTextView获得焦点时通过代码显示下拉列表,此时会自动根据已输入的内容进行对数据源进行筛选。

效果截图:

MultiAutoCompleteTextView

MultiAutoCompleteTextView是AutoCompleteTextView的直接子类,用法也几乎一致。区别在于,后者只能获得一次输入提示(一旦选择某一项就不再提示了),但是MultiAutoCompleteTextView通过分隔符可以获得多次输入提示。

基本用法

XML代码:

<MultiAutoCompleteTextView
    android:id="@+id/multi_auto_complete_text_view_style"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:completionThreshold="1"
    android:hint="请输入关键字"/>

设置Adapter和分隔符:

String[] dataResArray={"coding","ending","codingending","coding and ending"};
ArrayAdapter<String> multiDataAdapter=new ArrayAdapter<String>(getActivity(),
        android.R.layout.simple_list_item_1,dataResArray);
multiAutoCompleteTextView.setAdapter(multiDataAdapter);
//设置分隔符
multiAutoCompleteTextView.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());

CommaTokenizer是预设的分隔符对象,默认为英文逗号和若干个空格。

自定义分隔符

除了使用系统提供的CommaTokenizer,我们也可以定义自己的分隔符对象,只需要实现MultiAutoCompleteTextView.Tokenizer接口即可(大部分代码都可以参照CommaTokenizer的内容)。示例代码如下:

自定义的分隔符类:

public class StyleTokenizer implements MultiAutoCompleteTextView.Tokenizer{
    private char myTokenizer;//分隔符

    public StyleTokenizer(char myTokenizer) {
        this.myTokenizer = myTokenizer;
    }

    public int findTokenStart(CharSequence text, int cursor) {
        int i = cursor;

        while (i > 0 && text.charAt(i - 1) != myTokenizer) {
            i--;
        }
        while (i < cursor && text.charAt(i) == ' ') {
            i++;
        }

        return i;
    }

    public int findTokenEnd(CharSequence text, int cursor) {
        int i = cursor;
        int len = text.length();

        while (i < len) {
            if (text.charAt(i) == myTokenizer) {
                return i;
            } else {
                i++;
            }
        }

        return len;
    }

    public CharSequence terminateToken(CharSequence text) {
        int i = text.length();

        while (i > 0 && text.charAt(i - 1) == ' ') {
            i--;
        }

        if (i > 0 && text.charAt(i - 1) == myTokenizer) {
            return text;
        } else {
            if (text instanceof Spanned) {
                SpannableString sp = new SpannableString(text + String.valueOf(myTokenizer));
                TextUtils.copySpansFrom((Spanned) text, 0, text.length(),
                        Object.class, sp, 0);
                return sp;
            } else {
                return text + String.valueOf(myTokenizer);
            }
        }
    }
}

在代码中使用:

......
//设置自定义分隔符(#)
styleMultiAutoCompleteView.setTokenizer(new StyleTokenizer('#'));

效果截图:

最终的演示效果截图:

更多博客

《 Android UI 常用控件讲解》:包括CheckBox、RadioButton、ToggleButton、Switch、ProgressBar、SeekBar、RatingBar、Spinner、ImageButton。

demo下载地址

https://github.com/CodingEnding/UISystemDemo [ 持续更新中 ]

参考资料

https://www.jianshu.com/p/4e7d7a08fc7e
http://blog.csdn.net/u013699756/article/details/52294190
https://www.jianshu.com/p/2e9d54d761e7
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0120/2335.html
https://www.jianshu.com/p/5fe19793dd82
http://blog.csdn.net/u012702547/article/details/48708755
http://blog.csdn.net/a_long_/article/details/51011388

猜你喜欢

转载自blog.csdn.net/CodingEnding/article/details/79478477