标签: TextView

一个简单的Android富文本TextView实现

Android客户端富文本

现阶段问题

在Android客户端展现一个普通数据非常的方便,直接调用textview.setText()方法,但是当TextView比较复杂时(例如存在@用户,##话题,部分字符样式、网址链接等),普通的TextView就无法完成需求,需要自己封装一个富文本TextView。

Demo

Coding 冒泡示例

 

##

分析

1.一个TextView中不同类别的信息,需要由不同样式的显示,一般的用法textview.setTextColor(color)会将所有的文字变成同一种颜色,这显然是不符合要求的。为了实现这一个需求,我们将会用到SpannableStringBuilder这个类。

预备知识

SpannableStringBuilder:

基本概念

SpannableStringBuilder 和 StringBuilder类似,都可以存储字符串,不同的是SpannableStringBuilder有一个setSpan()函数,可以给存储的String添加不同的样式。如加下划线、背景色、字体颜色、字体大小等。

另外需要注意的是,当SpannableStringBuilder中存储了一个有样式的String,当把spannableStringBuilder展示在TextView、EditTextView中时,能显示这些样式;当展示在canvas上时,因为Canvas不支持SpannableStringBuilder的额外信息,所以会退化成一个普通的String,不显示样式信息。

setSpan()函数

void setSpan(Object what,int startIndex,int endIndex,int flag);

说明:

参数

说明

Object what

设置Span样式

int startIndex

样式开始的Index

int endIndex

样式结束的Index

int flag

新插入字符的样式设置

注意点:

  1. endIndex:字体样式结束的Index,该Index对应的字符不使用样式,比如有一个字符串为s = “abcd”,s.setSpan(span,0,2,flag),此时第0、1个字符ab使用了样式span,endIndex对应的字符c不使用。
  2. flag:取值如下

取值

说明

Spannable.SPAN_EXCLUSIVE_EXCLUSIVE

前后都不包括,即在指定范围的前面和后面插入新字符都不会应用新样式

Spannable.SPAN_EXCLUSIVE_INCLUSIVE

前面不包括,后面包括。即仅在范围字符的后面插入新字符时会应用新样式

Spannable.SPAN_INCLUSIVE_EXCLUSIVE

前面包括,后面不包括。

Spannable.SPAN_INCLUSIVE_INCLUSIVE

前后都包括

简单示例

1

2

3

4

5

6

//设置字体颜色

textview1 = (TextView) findViewById(R.id.text1);

SpannableStringBuilder spannableStringBuilder1 = new SpannableStringBuilder(“Android”);

ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(Color.BLUE);

spannableStringBuilder1.setSpan(foregroundColorSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

textview1.setText(spannableStringBuilder1);

效果:

 

多种Span

由以上的简单示例我们可以看出,设置一个样式的一般步骤是:

  1. 构造一个SpannableStringBuilder
  2. 构造一个Span,并设置在SpannableStringBuilder上
  3. 将SpannableStringBuilder绑定在TextView上展示

设置字体颜色

这个已经在简单示例中展示

设置字体背景颜色

1

2

3

4

5

6

//设置字体背景颜色

textview2 = (TextView) findViewById(R.id.text2);

SpannableStringBuilder spannableStringBuilder2 = new SpannableStringBuilder(“Android”);

BackgroundColorSpan backgroundColorSpan = new BackgroundColorSpan(Color.RED);

spannableStringBuilder2.setSpan(backgroundColorSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

textview2.setText(spannableStringBuilder2);

效果:

 

设置字体大小

1

2

3

4

5

6

//设置字体大小

textview3 = (TextView) findViewById(R.id.text3);

SpannableStringBuilder spannableStringBuilder3 = new SpannableStringBuilder(“Android”);

AbsoluteSizeSpan absoluteSizeSpan = new AbsoluteSizeSpan(30);

spannableStringBuilder3.setSpan(absoluteSizeSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

textview3.setText(spannableStringBuilder3);

效果:

 

设置字体

1

2

3

4

5

6

//设置字体(加粗斜体)

textview4 = (TextView) findViewById(R.id.text4);

SpannableStringBuilder spannableStringBuilder4 = new SpannableStringBuilder(“Android”);

StyleSpan styleSpan = new StyleSpan(Typeface.BOLD_ITALIC);

spannableStringBuilder4.setSpan(styleSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

textview4.setText(spannableStringBuilder4);

效果:

 

设置下划线

1

2

3

4

5

6

//设置下划线

textview5 = (TextView) findViewById(R.id.text5);

SpannableStringBuilder spannableStringBuilder5 = new SpannableStringBuilder(“Android”);

UnderlineSpan underlineSpan = new UnderlineSpan();

spannableStringBuilder5.setSpan(underlineSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

textview5.setText(spannableStringBuilder5);

效果:

 

设置下划线

1

2

3

4

5

6

//设置删除线

textview6 = (TextView) findViewById(R.id.text6);

SpannableStringBuilder spannableStringBuilder6 = new SpannableStringBuilder(“Android”);

StrikethroughSpan strikethroughSpan = new StrikethroughSpan();

spannableStringBuilder6.setSpan(strikethroughSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

textview6.setText(spannableStringBuilder6);

效果:

 

设置多种样式

除了设置一个样式以外,SpannableStringBuilder还支持设置多个样式。

1

2

3

4

5

6

7

8

9

//设置多种样式

textview7 = (TextView) findViewById(R.id.text7);

SpannableStringBuilder spannableStringBuilder7 = new SpannableStringBuilder(“Android”);

spannableStringBuilder7.setSpan(foregroundColorSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

spannableStringBuilder7.setSpan(backgroundColorSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

spannableStringBuilder7.setSpan(underlineSpan, 0, 3, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

spannableStringBuilder7.setSpan(absoluteSizeSpan, 3, 6, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

spannableStringBuilder7.setSpan(strikethroughSpan, 3, 6, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

textview7.setText(spannableStringBuilder7);

 

效果:

 

点击事件

实例代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(“Android”);

 

spannableStringBuilder.setSpan(

    new ClickableSpan() {

        @Override

        public void onClick(View widget) {

            //do something

        }

 

        @Override

        public void updateDrawState(TextPaint ds) {

            //设置一些样式

            //ds.setUnderlineText(false);

            //ds.setColor(color);

        }

    }, startIndex, endIndex,

    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE

);

 

简单实现

    1. 以Listview为例,我们需要实现的是每一个Item和Adapter中的getView()函数。
    2. 布局文件
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • <RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  •     android:layout_width=“match_parent” android:layout_height=”wrap_content”>
  •     <ImageView
  •         android:layout_width=“wrap_content”
  •         android:layout_height=“wrap_content”
  •         android:src=“@mipmap/ic_launcher”
  •         android:layout_marginTop=“16dp”
  •         android:layout_marginLeft=“16dp”
  •         android:id=“@+id/userhead”/>
  •     <TextView
  •         android:layout_width=“match_parent”
  •         android:layout_height=“wrap_content”
  •         android:layout_marginTop=“16dp”
  •         android:layout_marginLeft=“8dp”
  •         android:layout_marginRight=“16dp”
  •         android:layout_toRightOf=“@id/userhead”
  •         android:id=“@+id/content”/>
  • </RelativeLayout>

ImageView表示该Item的发布人,和富文本没有关系,在Demo中直接写死数据。

主要内容在TextView中

    1. 因为每一个TextView中可能有多个不同样式的文本(#话题#,@其他用户,文本内容等),所以先封装一个文本类:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • public class TextObject {
  •     private int mStartIndex;//样式的开始字符
  •     private int mEndIndex;//结束字符
  •     //private SpannableStringBuilder mSpannableStringBuilder;
  •     private String content;//文本内容
  •     private int mOptionType;//操作符
  •     private int color;//字体颜色
  •     private boolean isUnderline;//是否有下划线
  •     //…其他需要的属性可继承TextObject自行添加
  •   }

这个类封装的是一个Item中的某一个文本样式,所以我们还需要封装一个Item中的文本类:

1

2

3

4

5

6

public class Content {

 

    private List<TextObject> mList;

 

    private SpannableStringBuilder mSpannableStringBuilder;

  }

 

这个类中有一个List成员变量,存放着该Item中所有的样式文本。

对于成员变量SpannableStringBuilder,有一个init函数,用于根据List中的TextObject拼接StringBuilder

1

2

3

4

5

6

7

8

9

//拼接SpannableStringBuilder

public void initStringBuilder(){

 

    SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder();

    for (int i = 0;i<mList.size();i++){

        spannableStringBuilder.append(mList.get(i).getContent());

    }

    setmSpannableStringBuilder(spannableStringBuilder);

}

 

到现在,数据准备工作已经全部完成,现在就要显示这些数据了。

  1. getView()函数

getview函数中就可以通过content.getSpannableStringBuilder()函数获取到正文内容,然后即可根据上面的预备知识对其进行设置不同的样式和点击事件。

设置完成后,需要调用

1

viewHolder.textview.setMovementMethod(LinkMovementMethod.getInstance());

 

使其响应点击事件。

TextView设置行间距、行高,以及字间距

一. 设置TextView行间距、行高:
Android系统中TextView有默认行间距,但是比较窄有的时候需要我们设置每行行间距。
TextView为我们提供了相关设置属性android:lineSpacingExtra或android:lineSpacingMultiplier。
在xml文件设置:

1、android:lineSpacingExtra 设置行间距,如”10dp”。

表示额外的行间距数值,单位通常为dp,值可以为负数,小数和0。如果值为正数表示增加行间距;如果值为负数表示减少行间距;如果值为0,则没有 变化。

在java代码中

2、android:lineSpacingMultiplier 设置行间距的倍数,如”1.5或者2″

表示行间距的倍数,没有单位,值可以为任意浮点数。如果值大于1.0表示增加行间距,如果值小于1.0表示减少行间距。

3. android:lineSpacingExtra和android:lineSpacingMultiplier

可以在一起对同一个TextView进行设置,同时使用时会先增加android:lineSpacingMultiplier设置的倍数,再加上android:lineSpacingExtra设置的额外间距

代码示例:

<TextView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”对Android系统中TextView设置行间距,请看效果”
android:textColor=”#ff00″
android:lineSpacingExtra=”15dp”
android:lineSpacingMultiplier=”3″
android:textSize=”20sp”
/>

在java文件里进行设置:

1、setLineSpacing (float add, float mult)

参数add表示要增加的间距数值,对应android:lineSpacingExtra参数。参数mult表示要增加的间距倍数,对应android:lineSpacingMultiplier参数。

代码示例:

TextView tv_textview = (TextView) findViewById(R.id.tv_textview);
tv_textview.setLineSpacing(15,3);

 

二. 设置TextView字间距:
一般会用android:textScaleX可以设置文字间距,但是它是控制字体水平方向的缩放,即水平方向放大。并不是咱们所需要的字间距的放大

代码示例:

<TextView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”对Textview设置行间距,请看效果”
android:textColor=”#ff00″
android:textSize=”20sp”
/>
<TextView
android:layout_marginTop=”20dp”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”对Textview设置行间距,请看效果”
android:textScaleX=”2″
android:textColor=”#ff00″
android:textSize=”20sp”
/>
设置
android:textScaleX=”2″
我们看下android:textScaleX设置的效果:

%title插图%num

这不是我们所需要的扩大字间距,TextView没有提供相应的方法。既然没有直接的方法,我们可以通过别的方式实现,这儿是通过添加空格来实现的。

下面提供设置字间距 代码:

自定义TextView:

import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ScaleXSpan;
import android.util.AttributeSet;
import android.widget.TextView;

/**
* Created by shanshan on 2018/3/15.
*/

public class ChangeTextViewSpace extends TextView {
private float spacing = Spacing.NORMAL;
private CharSequence originalText = “”;
public ChangeTextViewSpace(Context context) {
super(context);
}

public ChangeTextViewSpace(Context context, AttributeSet attrs) {
super(context, attrs);
}

public ChangeTextViewSpace(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

/**
* 获取字间距
*/
public float getSpacing() {
return this.spacing;
}

/**
* 设置间距
*/
public void setSpacing(float spacing) {
this.spacing = spacing;
applySpacing();
}

@Override
public void setText(CharSequence text, BufferType type) {
originalText = text;
applySpacing();
}

@Override
public CharSequence getText() {
return originalText;
}

/**
* 扩大文字空间
*/
private void applySpacing() {
if (this == null || this.originalText == null) return;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < originalText.length(); i++) {
builder.append(originalText.charAt(i));
if (i + 1 < originalText.length()) {
//如果前后都是英文,则不添加空格,防止英文空格太大
if (isEnglish(originalText.charAt(i) + “”) && isEnglish(originalText.charAt(i + 1) + “”)) {
} else {
// \u00A0 不间断空格 碰见文字追加空格
builder.append(“\u00A0”);
}
}
}
// 通过SpannableString类,去设置空格
SpannableString finalText = new SpannableString(builder.toString());
// 如果当前TextView内容长度大于1,则进行空格添加
if (builder.toString().length() > 1) {
for (int i = 1; i < builder.toString().length(); i += 2) {
// ScaleXSpan 基于x轴缩放 按照x轴等比例进行缩放 通过字间距+1除以10进行等比缩放
finalText.setSpan(new ScaleXSpan((spacing + 1) / 10), i, i + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
super.setText(finalText, BufferType.SPANNABLE);
}

public class Spacing {
public final static float NORMAL = 0;
}
/**
* 判断是否是英语
*/
public static boolean isEnglish(String charaString) {
return charaString.matches(“^[a-zA-Z]*”);
}

}
xml文件代码:

<com.ss.myapplication.ChangeTextViewSpace
android:id=”@+id/tv_tv”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:textColor=”#ff00″
android:textSize=”20sp”
/>
java 文件代码:

ChangeTextViewSpace changeTextViewSpace = (ChangeTextViewSpace) findViewById(R.id.tv_tv);
changeTextViewSpace.setSpacing(10);
changeTextViewSpace.setText(“对Textview设置行间距,请看效果”);
注意:这儿setText()调用的是自定义textview的方法,不能在xml文件中设置。

这儿对英文进行了处理,防止每个字母扩大间距;

效果图:

%title插图%num
————————————————

自定义TextView设置字间距

兼容5.0以下

public class MyView extends TextView{
private float spacing = Spacing.NORMAL;
private CharSequence originalText = “”;

public MyView(Context context) {
super(context);
}

public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}

public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}

public float getSpacing() {
return this.spacing;
}

private void init(Context context, AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LetterSpacingTextView);
originalText = array.getString(R.styleable.LetterSpacingTextView_text);
setSpacing(array.getFloat(R.styleable.LetterSpacingTextView_textSpacing, 0));
array.recycle();
}

public void setSpacing(float spacing) {
this.spacing = spacing;
applySpacing();
}

@Override
public void setText(CharSequence text, BufferType type) {
originalText = text;
applySpacing();
}

@Override
public CharSequence getText() {
return originalText;
}

private void applySpacing() {
if (this == null || this.originalText == null) return;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < originalText.length(); i++) {
builder.append(originalText.charAt(i));
if (i + 1 < originalText.length()) {
builder.append(“\u00A0″);
}
}
SpannableString finalText = new SpannableString(builder.toString());
if (builder.toString().length() > 1) {
for (int i = 1; i < builder.toString().length(); i += 2) {
finalText.setSpan(new ScaleXSpan((spacing + 1) / 10), i, i + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
super.setText(finalText, BufferType.SPANNABLE);
}

public class Spacing {
public final static float NORMAL = 0;
}

}

attrs.xml

<resources>

<declare-styleable name=”LetterSpacingTextView”>
<attr name=”textSpacing” format=”float”/>
<attr name=”text” format=”string”/>
</declare-styleable>
</resources>

引用
注意 text属性要用app

<com.kk.khapplication.view.MyView

android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
app:text=”何须浅碧轻红色自是花中第”
app:textSpacing=”20″

/>
————————————————

android textView调整字体的间距和行间距

字间距

textView有一个属性android:textScaleX是调节字间距的,它的值是一个float型。查看源代码,默认textView 此属性是使用的是:

android.internal.R.styleable.TextView_textScaleX

setTextScaleX(a.getFloat(attr, 1.0f));

这个用了之后我们发现,textScaleX不是调整字间距的,而是调整字的水平拉申。

行间距

Android系统中TextView默认显示中文时会比较紧凑,不是很美观。为了让每行保持一定的行间距,可以设置属性android:lineSpacingExtra或android:lineSpacingMultiplier。

关于Android下TextView中文换行问题,可查看Android自定义view-文本自动换行。

1、android:lineSpacingExtra
设置行间距,如”3dp”。

2、android:lineSpacingMultiplier
设置行间距的倍数,如”1.2″。

参考代码:

<TextView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:lineSpacingExtra=”3dp”
android:lineSpacingMultiplier=”1.5″
android:textStyle=”bold” />

其实内容很简单,大家自己可以去查找资料。

android textView调整字体的间距和行间距 就讲完了。

就这么简单

————————————————

Android TextView 设置文字间距

搜索结果都不靠谱,基本上都说是android:textScaleX可以设置文字间距,也不知道你们到底有没有真正做过开发。

 

Android开发文档里描述的很清楚:

android:textScaleX

Sets the horizontal scaling factor for the text.

这个东西是用来设置水平方向的文字放大和缩小的。

 

其实设置文字间距可以通过修改string.xml里的字符,在其中添加空格来实现。

但是在字符串中直接输入空格达不到想要的效果,这就需要使用转义字符来进行转义,这样才能在使用时正确显示字符。

 

常用的XML转义字符记录如下:

空格: <string name=”out_bound_submit”>出  库</string> 其中的 就代表空格

       换行: <string name=”hello_world”>你好!\n世界!</string> 其中的\n就代表换行

       缩进: <string name=”hello_world”>你好!\t世界!</string> 其中的\t就代表按一次Tab键的几个空格。

应当注意,由于系统定义的基本的缩进的格数不同,有的代表4个半角字符,有的代表8个半角字符,所以可能显示时效果不同,建议如果编写界面时尽量少用。

Android 中如何调节 TextView 的字间距

当前版本的 Android 似乎并未提供控制 TextView 的字间距 方法。
搜索网上发现大量“教程”声称可以利用 TextView 的  setTextScaleX() 方法设置字间距,但从字面上(Scale)就可看出其实它是用于设置字体的缩放比率(试验结果亦是如此)。
*后从国外的一家论坛上发现一个解决方案:通过继承 TextView 并重写  setText() 和  getText()   方法,增加  setLetterSpacing() 等方法搞定该需求。
代码整理如下:
代码例——
  1. /**
  2. * 示例:设置 TextView 的字间距
  3. * @author Pedro Barros (pedrobarros.dev at gmail.com)
  4. * @since May 7, 2013
  5. */
  6. import android.content.Context;
  7. import android.text.Spannable;
  8. import android.text.SpannableString;
  9. import android.text.style.ScaleXSpan;
  10. import android.util.AttributeSet;
  11. import android.widget.TextView;
  12. public class LetterSpacingTextView extends TextView {
  13. private float letterSpacing = LetterSpacing.NORMAL;
  14. private CharSequence originalText = “”;
  15. public LetterSpacingTextView(Context context) {
  16. super(context);
  17. }
  18. public LetterSpacingTextView(Context context, AttributeSet attrs){
  19. super(context, attrs);
  20. }
  21. public LetterSpacingTextView(Context context, AttributeSet attrs, int defStyle){
  22. super(context, attrs, defStyle);
  23. }
  24. public float getLetterSpacing() {
  25. return letterSpacing;
  26. }
  27. public void setLetterSpacing(float letterSpacing) {
  28. this.letterSpacing = letterSpacing;
  29. applyLetterSpacing();
  30. }
  31. @Override
  32. public void setText(CharSequence text, BufferType type) {
  33. originalText = text;
  34. applyLetterSpacing();
  35. }
  36. @Override
  37. public CharSequence getText() {
  38. return originalText;
  39. }
  40. private void applyLetterSpacing() {
  41. StringBuilder builder = new StringBuilder();
  42. for(int i = 0; i < originalText.length(); i++) {
  43. builder.append(originalText.charAt(i));
  44. if(i+1 < originalText.length()) {
  45. builder.append(“\u00A0”);
  46. }
  47. }
  48. SpannableString finalText = new SpannableString(builder.toString());
  49. if(builder.toString().length() > 1) {
  50. for(int i = 1; i < builder.toString().length(); i+=2) {
  51. finalText.setSpan(new ScaleXSpan((letterSpacing+1)/10), i, i+1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  52. }
  53. }
  54. super.setText(finalText, BufferType.SPANNABLE);
  55. }
  56. public class LetterSpacing {
  57. public final static float NORMAL = 0;
  58. }
  59. }

应用例——

  1. LetterSpacingTextView textView = new LetterSpacingTextView(context);
  2. textView.setLetterSpacing(10); //参数为 float 类型。可另设其他值如 0 或者默认值 LetterSpacingTextView.LetterSpacing.NORMAL
  3. textView.setText(“My text”);
  4. //Add the textView in a layout, for instance:
  5. ((LinearLayout) findViewById(R.id.myLinearLayout)).addView(textView);

Android BMI体质计算器实现

本文源于安卓基础,主要介绍如何实现BMI体质计算器。

先讲规则后讲实现

体质计算规则

胖瘦程度

体质指数

过轻

男性低于20,女性低于19

适中

男性20-25,女性19-24

超重

男性25-30,女性24-29

肥胖

男性30-35,女性29-34

严重肥胖

男性高于35,女性高于34

测试效果

案例打印出保留两位BMI指数,并且对于身高体重小于0,异常不处理!

实例步骤

创建项目My eleApplication

点进Project—>Empty Activity—>然后名字改下,finish即可。成功之后,点击箭头运行程序。

程序正常可以跑成功hello world字样,下面我们继续

布局activity_main.xml

大家从实验效果可以看出,这里用了不少控件。三个TextView,两个EditText,两个单选按钮控件,一个按钮控件,代码下方对内容作进一步分析!

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:orientation=”vertical”>

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:text=”身高(cm)”

android:id=”@+id/txt1″/>

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:id=”@+id/edX”/>

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:text=”体重(kg)”

android:id=”@+id/txt2″/>

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:id=”@+id/edY”/>

android:layout_width=”match_parent”

android:layout_height=”wrap_content”

android:orientation=”horizontal”

android:id=”@+id/rg”>

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:text=”男”

android:id=”@+id/rb1″

android:layout_marginRight=”30dp”

android:checked=”true”/>

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:text=”女”

android:id=”@+id/rb2″

/>

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:id=”@+id/btn”

android:text=”计算BMI值”/>

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:id=”@+id/tvResult”/>

LinearLayout详解

这是基础的线性布局,只需要配置width与height。水平方向即可

TextView详解

我们用了三个,其中一个是输出结果,另外两个只是为了代码更友好。具体的也是基础设置。

RadioButton详解

要想使用单选按钮必须要设置按钮组,然后一些基本的配置,包括text,id,width与height。id是后期获得操作的。必须设置

EditText详解

很多时候,编辑文本是在初学者时期需要用到的。因此id必须设置

搭建MainActivity.java代码

java代码在完成这个案例时只需要三步,这也是翁恺老师经常说的三步:“输入-处理–输出”。输入就是指获取View种的所有对象。处理就是指能根据输入值找出判断依据,输出就是把答案抛出来。

package com.example.myeleapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.app.Activity;

import android.os.Bundle;

import android.view.View;

import android.widget.Button;

import android.widget.EditText;

import android.widget.RadioButton;

import android.widget.TextView;

import org.w3c.dom.Text;

public class MainActivity extends Activity implements View.OnClickListener {
RadioButton rb1;

RadioButton rb2;

TextView tvResult;

EditText txt1;

EditText txt2;

Button btn;

@Override

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

setViews();

}

public void setViews() {
rb1 = (RadioButton) findViewById(R.id.rb1);

rb2 = (RadioButton) findViewById(R.id.rb2);//判断男女

tvResult = (TextView) findViewById(R.id.tvResult);

txt1 = (EditText)findViewById(R.id.edX);

txt2 = (EditText)findViewById(R.id.edY);

btn = (Button)findViewById(R.id.btn);

btn.setOnClickListener(this);

}

@Override

public void onClick(View v) {
double x = Double.parseDouble(txt1.getText().toString());//身高

double y = Double.parseDouble(txt2.getText().toString());//体重//体重除以身高的平方

double res;

String str = “Your BMI is”;

if(x<=0 || y<=0) {
tvResult.setText(“值异常,不计算”);

return ;

}

x = x/100;

res = y / (x*x);

String str1 = String.format(“%.2f”,res);

str = str + str1;

if(rb1.isChecked())

res -= 1;//以女性为标准进行比较

//仅以女性作为评价标准

str += “体型:”;

if(res < 19)

str += “过轻”;

else if(res < 24)

str += “适中”;

else if(res<29)

str += “超重”;

else if(res<34)

str += “肥胖”;

else

str += “严重肥胖”;

tvResult.setText(str);

}

}

点击运行,获取结果

总结

执念并在此总结了实例的步骤:

创建项目,跑通hello world

搭建界面

配置java代码

点击运行,收获喜悦

希望本文能帮助到大家!
————————————————

android 如何动态更新textview的内容

android 如何动态更新textview的内容_【Android设计模式】

从Retrofit看懂动态代理模式,看大牛如何解决

前言

平时写代码的时候可能为了完成某一个任务而只是应付性地编码,然后写完理直气壮地来一句”又不是不能用!”,但如果要把编码当作一项艺术来打造,那就需要结合我们的设计模式了。设计模式可以运用在诸多方面,让我们的代码更加优雅。

设计模式在Android中也是无处不在,动态代理、建造者、工厂、适配器….等等等等,可见它对我们的重要性。*近在看Retrofit的源码,刚好看到了动态代理,想着总结下这个模式。

什么是代理模式?

代理类在程序运行前不存在、运行时由程序动态生成的代理方式称为动态代理。

乍这么一看,好像还是说不明白,代理模式分为两种,静态代理和动态代理,静态代理是由我们手动编写代理类,动态代理是在运行时由程序生成的代理类,二者的*终目的都是为了代理另外一个类(被代理类)的功能。以我们日常代购火车票为例,这里先简要了解下静态代理模式,方便理解动态代理。

静态代理模式

首先定义一个接口,声明被代理的类所需要实现的功能

/** * 买票接口 */public interface IBuy {    //买票    void buyTicket();}

定义被代理类(火车站),实现买票功能:

/** * 被代理类 */public class TrainStation implements IBuy{    private String destination;    public TrainStation(String destination) {        this.destination = destination;    }    @Override    public void buyTicket() {        Log.d("Proxy", "在火车站售票处买到了去" + destination + "的火车票");    }}

定义代理类,实现同一个接口:

/** * 票贩子 代理火车站卖票 */public class TrainStationProxy implements IBuy{    private String destination;    //持有火车站(相当于能直接访问火车站内部)    private TrainStation trainStation;    public TrainStationProxy(String destination) {        this.destination = destination;    }    @Override    public void buyTicket() {        if(trainStation == null){            trainStation = new TrainStation(destination);        }        //本质还是通过火车站买票        trainStation.buyTicket();        Log.d("Proxy", "通过票贩子买到了去"+ destination + "的火车票");    }}

客户端调用:

//客户端通过票贩子,间接买到了票IBuy proxy = new TrainStationProxy("北京");proxy.buyTicket();

通过上面的例子,可以看出票贩子就是一个火车站的代理,客户端本质上买的票其实还是火车站的,但是是通过票贩子来帮我们代理买票这个动作,以上就是静态代理模式。

为什么要使用动态代理?

静态代理模式实现了我们通过票贩子买票的功能,但票贩子这个代理类需要我们手动编码定义,如果需要代理的对象多了,比如有一百多个需要代理的功能(代理进站、代理托运、代理换乘…等等),那岂不是要建一百多个代理类,这个时候就需要我们的动态代理模式了,动态代理与静态代理*大的区别是,在运行时让虚拟机帮我们生成一个对应的代理类,来调用对应的方法,并且在使用结束后回收,解决了静态代理的局限性。

如何实现动态代理?

动态代理模式就不需要我们定义代理类了,但需要借助 InvocationHandler 这个类来实现,主要有如下几个步骤:

1.声明调用处理器类InvocationHandler2.声明目标对象类的抽象接口3.声明目标对象类4.动态生成代理对象,通过代理对象来调用目标对象的方法

1. 声明调用处理器类InvocationHandler

/** * Created by Y on 2019/2/25. */public class DynamicProxy implements InvocationHandler {    private Object proxyObject;    public Object newProxyInstance(Object proxyObject) {        this.proxyObject = proxyObject;        return Proxy.newProxyInstance(proxyObject.getClass().getClassLoader(),                proxyObject.getClass().getInterfaces(), this);    }    @Override    public Object invoke(Object proxy, Method method, Object[] args)            throws Throwable {        Log.d("Dynamic", "代理对象准备开始调用目标对象方法");        Object result = method.invoke(proxyObject, args);        Log.d("Dynamic", "代理对象调用目标对象方法完毕");        return result;    }}
f7c3625ba51117bcbaa4ab1904c1ded6.png

可以看到,newProxyInstance方法中,调用了Proxy.newPorxyInstance方法,方法参数如下:

Object newProxyInstance(ClassLoader loader, Class>[] interfaces,InvocationHandler h)

这个方法有什么用呢?先说下这几个参数的意义

ClassLoader loader:指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器Class>[] interfaces:指定目标对象的实现接口,即要给目标对象提供一组什么接口。若提供了一组接口给它,那么该代理对象就默认实现了该接口,这样就能调用这组接口中的方法InvocationHandler h:指定InvocationHandler对象。即动态代理对象在调用方法时,会关联到哪个InvocationHandler对象

可以看到头两个对象传进去了目标类的ClassLoader对象,以及它所声明的接口,到时候程序运行起来就会在Proxy.newPorxyInstance内部通过反射来生成目标类的实例,并且提供一组接口给它实现,到时候外界需要这些接口的时候才能调用(就类似于刚才举例的Retrofit的ServiceApi.interface)。

然后将我们的InvocationHandler对象传进去,代理对象在调用方法时内部就会使用该对象调用invoke(),进而回调回我们的invoke方法。

上面通过newProxyInstance生成代理实例,那invoke自然就是用来代理调用目标方法,method.invoke同样是代理类运用反射调用目标方法并返回结果。

2. 声明目标对象类的抽象接口

public interface TestInterface {    void test();} 

3. 声明目标对象类

public class TestImpl implements TestInterface{    @Override    public void test() {        Log.d("Android小Y", "调用了目标类的test方法");    }}

4. 通过动态代理对象,调用目标对象的方法

// 1. 创建调用处理器类对象DynamicProxy dynamicProxy = new DynamicProxy();// 2. 创建目标对象对象TestImpl testImpl = new TestImpl();// 3. 通过调用处理器类对象newProxyInstance创建动态代理类对象TestInterface proxy = (TestInterface) dynamicProxy.newProxyInstance(testImpl);        //代理调用test,从而调用到了invoke,*后调用到了目标类的test方法proxy.test();

运行结果:

02-15 15:48:30.099 6578-6578/com.example.test.main D/Android小Y: 代理对象准备开始调用目标对象方法02-15 15:48:30.101 6578-6578/com.example.test.main D/Android小Y: 调用了目标类的test方法02-15 15:48:30.101 6578-6578/com.example.test.main D/Android小Y: 代理对象调用目标对象方法完毕

可以看到,动态代理模式下,我们只需要将具体被代理类传给处理器,即可为我们动态生成对应的代理类,不再需要像静态代理那样繁琐。

Retrofit中的动态代理

用过Retrofit网络请求库的朋友都知道,Retrofit的基本用法就是:

1. 先定义一个接口:

public interface ServerApi {    @GET("xxxxx/xxx/xxx")    Call getData();}

2. 接着创建Retrofit实例:

Retrofit retrofit = new Retrofit.Builder()                .baseUrl("http://xxx.xxx.com/") //设置网络请求的Url地址                .addConverterFactory(GsonConverterFactory.create()) //设置数据解析器                .build();

3. 发起请求

ServerApi request = retrofit.create(ServerApi.class); //创建代理实例request.getData(); //发送请求

注意第三步骤,表面上,传进去我们接口的类对象即可生成对应的实例并调用getData,是因为这里面实际上retrofit利用动态代理模式生成了一个代理对象(并且实现了ServiceApi),然后一旦调用了ServiceApi的方法,就会触发代理对象的invoke方法,从而在invoke总拦截并获得了ServiceApi的所有方法以及对应的那些@GET@POST等Retrofit的注解信息,然后利用OKhttp完成真正的请求操作。

我们从Retrofit的create方法一探究竟:

public  T create(final Class service) {    Utils.validateServiceInterface(service);    if (validateEagerly) {      eagerlyValidateMethods(service);    }    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class>[] { service },        new InvocationHandler() {          private final Platform platform = Platform.get();          @Override public Object invoke(Object proxy, Method method, Object... args)              throws Throwable {            // If the method is a method from Object then defer to normal invocation.            if (method.getDeclaringClass() == Object.class) {              return method.invoke(this, args);            }            if (platform.isDefaultMethod(method)) {              return platform.invokeDefaultMethod(method, service, proxy, args);            }            ServiceMethod serviceMethod = loadServiceMethod(method);            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);            return serviceMethod.callAdapter.adapt(okHttpCall);          }        });}

可以看到,也是通过Proxy.newProxyInstance生成了代理类,并new了一个匿名内部InvocationHandler对象,重写它的invoke方法,相当于通过我们传进来的接口,生成了一个接口的代理类,并且实现了接口是所有方法(因为InvocationHandler的参数里传进去了new Class>[] { service }),然后返回给外部。

外部一旦调用了接口方法,例如request.getData();,就会触发InvocationHandler的invoke,从而根据注解生成Retrofit的ServiceMethod和OkHttpCall对象,这些都是发起网络请求的必备信息,然后*终发起请求。

那么Retrofit为何要这么设计呢?

我们一个应用的请求接口可能有很多个,通过动态代理模式,能动态为每个接口都生成一个具体的代理类,并且实现了我们的接口,我们不需要关心具体请求细节是怎样的,只需要声明我们的接口并传递给Retrofit即可,然后由Retrofit动态生成具体请求对象,发起请求并将结果返回给我们,我想这也就是为何Retrofit这么受欢迎的原因之一。

总结

综上,一般为了在不修改原有类的情况下扩展其功能,并且保护被代理类不被访问,我们可以选择代理模式。动态代理模式是通过动态生成代理类的代理模式,它其实就是将目标类的类加载器和相应的接口传进去代理器(InvocationHandler),通过代理器生成了一份新的”备份“——代理类,这个动态生成的代理类里实现了接口中的所有方法,然后在 InvocationHandler.invoke() 中通过反射机制,调用目标类对象的方法。

动态代理运用了很多反射,在性能上会有所影响,所以并不是随处滥用,用得得当会有很好的效果,比如常说的AOP切面编程,实现无侵入式的代码扩展。

 

android 如何动态更新textview的内容_Android桌面小部件AppWidget开发

什么是AppWidget

AppWidget 即桌面小部件,也叫桌面控件,就是能直接显示在Android系统桌面上的小程序,先看图:

1b5b2b8215038e09b70a204151be4574.png

图中我用黄色箭头指示的即为AppWidget,一些用户使用比较频繁的程序,可以做成AppWidget,这样能方便地使用。典型的程序有时钟、天气、音乐播放器等。AppWidget 是Android 系统应用开发层面的一部分,有着特殊用途,使用得当的化,的确会为app 增色不少,它的工作原理是把一个进程的控件嵌入到别外一个进程的窗口里的一种方法。长按桌面空白处,会出现一个 AppWidget 的文件夹,在里面找到相应的 AppWidget ,长按拖出,即可将 AppWidget 添加到桌面,

如何开发AppWidget

AppWidget 是通过 BroadCastReceiver 的形式进行控制的,开发 AppWidget 的主要类为 AppWidgetProvider, 该类继承自 BroadCastReceiver。为了实现桌面小部件,开发者只要开发一个继承自 AppWidgetProvider 的子类,并重写它的 onUpdate() 方法即可。重写该方法,一般来说可按如下几个步骤进行:

1、创建一个 RemoteViews 对象,这个对象加载时指定了桌面小部件的界面布局文件。

2、设置 RemoteViews 创建时加载的布局文件中各个元素的属性。

3、创建一个 ComponentName 对象

4、调用 AppWidgetManager 更新桌面小部件。

下面来看一个实际的例子,用 Android Studio 自动生成的例子来说。

新建了一个 HelloWorld 项目,然后新建一个 AppWidget ,命名为 MyAppWidgetProvider,按默认下一步,就完成了一个*简单的AppWidget的开发。运行程序之后,将小部件添加到桌面。操作步骤和默认效果如下:

aef6c4b71a6fbf3b74837b90bc6f5ac0.png
83e21ccd36c7a25aec923e66a205b224.png

我们看看 AS 为我们自动生成了哪些代码呢?对照着上面说的的步骤我们来看看。

首先,有一个 MyAppWidgetProvider 的类。

package com.example.joy.remoteviewstest; import android.appwidget.AppWidgetManager;import android.appwidget.AppWidgetProvider;import android.content.Context;import android.widget.RemoteViews; /** * Implementation of App Widget functionality. */public class MyAppWidgetProvider extends AppWidgetProvider {  static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {  CharSequence widgetText = context.getString(R.string.appwidget_text);   // Construct the RemoteViews object RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_app_widget_provider); views.setTextViewText(R.id.appwidget_text, widgetText);  // Instruct the widget manager to update the widget appWidgetManager.updateAppWidget(appWidgetId, views); }  @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // There may be multiple widgets active, so update all of them for (int appWidgetId : appWidgetIds) { updateAppWidget(context, appWidgetManager, appWidgetId); } }  @Override public void onEnabled(Context context) { // Enter relevant functionality for when the first widget is created }  @Override public void onDisabled(Context context) { // Enter relevant functionality for when the last widget is disabled }}

该类继承自 AppWidgetProvider ,AS默认帮我们重写 onUpdate() 方法,遍历 appWidgetIds, 调用了 updateAppWidget() 方法。再看 updateAppWidget() 方法,很简单,只有四行:

*行,CharSequence widgetText = context.getString(R.string.appwidget_text);声明了一个字符串;

第二行,RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_app_widget_provider);

创建了一个 RemoteViews 对象,*个参数传应用程序包名,第二个参数指定了,RemoteViews 加载的布局文件。这一行对应上面步骤中说的*点。可以看到在 res/layout/ 目录下面 AS 自动生成了一个 my_app_widget_provider.xml 文件,内容如下:

```````RelativeLayout``>`

这个文件就是我们*后看到的桌面小部件的样子,布局文件中只有一个TextView。这是你可能会问,想要加图片可以吗?可以,就像正常的Activity布局一样添加 ImageView 就行了,聪明的你可能开始想自定义小部件的样式了,添加功能强大外观漂亮逼格高的自定义控件了,很遗憾,不可以。小部件布局文件可以添加的组件是有限制的,详细内容在下文介绍RemoteViews 时再说。

第三行,views.setTextViewText(R.id.appwidget_text, widgetText);

将*行声明的字符串赋值给上面布局文件中的 TextView,注意这里赋值时,指定TextView的 id,要对应起来。这一行对于了上面步骤中的第二点。

第四行,appWidgetManager.updateAppWidget(appWidgetId, views);

这里调用了 appWidgetManager.updateAppWidget() 方法,更新小部件。这一行对应了上面步骤中的第四点。

这时,你可能有疑问了,上面明明说了四个步骤,其中第三步,创建一个 ComponentName 对象,明明就不需要。的确,这个例子中也没有用到。如果我们手敲第四步代码,AS的智能提示会告诉你,appWidgetManager.updateAppWidget() 有三个重载的方法。源码中三个方法没有写在一起,为了方便,这里我复制贴出官方 API 中的介绍

void  updateAppWidget(ComponentName provider, RemoteViews views)Set the RemoteViews to use for all AppWidget instances for the supplied AppWidget provider. void  updateAppWidget(int[] appWidgetIds, RemoteViews views)Set the RemoteViews to use for the specified appWidgetIds.void updateAppWidget(int appWidgetId, RemoteViews views)Set the RemoteViews to use for the specified appWidgetId.

这个三个方法都接收两个参数,第二个参数都是 RemoteViews 对象。其中*个方法的*个参数就是 ComponentName 对象,更新所有的 AppWidgetProvider 提供的所有的 AppWidget 实例,第二个方法时更新明确指定 Id 的 AppWidget 的对象集,第三个方法,更新明确指定 Id 的某个 AppWidget 对象。所以一般我们使用*个方法,针对所有的 AppWidget 对象,我们也可以根据需要选择性地去更新。

到这里,所有步骤都结束了,就完了?还没。前面说了,自定义的 MyAppWidgetProvider 继承自 AppWidgetProvider,而 AppWidgetProvider 又是继承自 BroadCastReceiver,

所以说 MyAppWidgetProvider 本质上是一个广播接受者,属于四大组件之一,需要我们的清单文件中注册。打开AndroidManifest.xml文件可以看到,的确是注册了小部件的,内容如下:

       

上面代码中有一个 Action,这个 Action 必须要加,且不能更改,属于系统规范,是作为小部件的标识而存在的。如果不加,这个 Receiver 就不会出现在小部件列表里面。然后看到小部件指定了 @xml/my_app_widget_provider_info 作为meta-data,细心的你发现了,在 res/ 目录下面建立了一个 xml 文件夹,下面新建了一个 my_app_widget_provider_info.xml 文件,内容如下:

```xml` `version``=``"1.0"` `encoding``=``"utf-8"``?>``````appwidget-provider``>`

这里配置了一些小部件的基本信息,常用的属性有 initialLayout 就是小部件的初始化布局, minHeight 定义了小部件的*小高度,previewImage 指定了小部件在小部件列表里的预览图,updatePeriodMillis 指定了小部件更新周期,单位为毫秒。更多属性,可以查看API文档。

到这里,上面这个*简单的小部件开发过程就真的结束了。为了开发出更强大一点小部件,我们还需要进一步了解 RemoteViews 和 AppWidgetProvider。

AppWidget的妆容——RemoteViews

下面简单说说 RemoteViews 相关的几个类。

1.1 RemoteViews

RemoteViews,从字面意思理解为它是一个远程视图。是一种远程的 View,它在其它进程中显示,却可以在另一个进程中更新。RemoteViews 在Android中的使用场景主要有:自定义通知栏和桌面小部件。

在RemoteViews 的构造函数中,第二个参数接收一个 layout 文件来确定 RemoteViews 的视图;然后,我们调用RemoteViews 中的 set 方法对 layout 中的各个组件进行设置,例如,可以调用 setTextViewText() 来设置 TextView 组件的文本。

前面提到,小部件布局文件可以添加的组件是有限制的,它可以支持的 View 类型包括四种布局:FrameLayout、LinearLayout、RelativeLayout、GridLayout 和 13 种View: AnalogClock、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextView、ViewFlipper、ListView、GridView、StackView、AdapterViewFlipper、ViewSub。注意:RemoteViews 也并不支持上述 View 的子类。

RemoteViews 提供了一系列 setXXX() 方法来为小部件的子视图设置属性。具体可以参考 API 文档。

1.2 RemoteViewsService

RemoteViewsService,是管理RemoteViews的服务。一般,当AppWidget 中包含 GridView、ListView、StackView 等集合视图时,才需要使用RemoteViewsService来进行更新、管理。RemoteViewsService 更新集合视图的一般步骤是:

(01) 通过 setRemoteAdapter() 方法来设置 RemoteViews 对应 RemoteViewsService 。

(02) 之后在 RemoteViewsService 中,实现 RemoteViewsFactory 接口。然后,在 RemoteViewsFactory 接口中对集合视图的各个子项进行设置,例如 ListView 中的每一Item。

1.3 RemoteViewsFactory

通过RemoteViewsService中的介绍,我们知道RemoteViewsService是通过 RemoteViewsFactory来具体管理layout中集合视图的,RemoteViewsFactory是RemoteViewsService中的一个内部接口。RemoteViewsFactory提供了一系列的方法管理集合视图中的每一项。例如:

RemoteViews getViewAt(int position)

通过getViewAt()来获取“集合视图”中的第position项的视图,视图是以RemoteViews的对象返回的。

int getCount()

通过getCount()来获取“集合视图”中所有子项的总数。

AppWidget的美貌——AppWidgetProvider

我们说一位女同事漂亮,除了因为她穿的衣服、化的妆漂亮以外,我想*主要的原因还是她本人长的漂亮吧。同样,小部件之所以有附着在桌面,跨进程更新 View 的能力,主要是因为AppWidgetProvider 是一个广播接收者。

我们发现,上面的例子中,AS 帮我们自动生成的代码中,除了 onUpdate() 方法被我们重写了,还有重写 onEnable() 和 onDisable() 两个方法,但都是空实现,这两个方法什么时候会被调用?还有,我们说自定义的 MyAppWidgetProvider,继承自 AppWidgetProvider,而 MyAppWidgetProvider 又是BroadCastReceiver 的子类,而我们却没有向写常规广播接收者一样重写 onReceiver() 方法?下面跟进去 AppWidgetProvider 源码,一探究竟。

这个类代码并不多,其实,AppWidgetProvider 出去构造方法外,总共只有下面这些方法:

onEnable() :当小部件*次被添加到桌面时回调该方法,可添加多次,但只在*次调用。对用广播的 Action 为 ACTION_APPWIDGET_ENABLE。

onUpdate(): 当小部件被添加时或者每次小部件更新时都会调用一次该方法,配置文件中配置小部件的更新周期 updatePeriodMillis,每次更新都会调用。对应广播 Action 为:ACTION_APPWIDGET_UPDATE 和 ACTION_APPWIDGET_RESTORED 。

onDisabled(): 当*后一个该类型的小部件从桌面移除时调用,对应的广播的 Action 为 ACTION_APPWIDGET_DISABLED。

onDeleted(): 每删除一个小部件就调用一次。对应的广播的 Action 为: ACTION_APPWIDGET_DELETED 。

onRestored(): 当小部件从备份中还原,或者恢复设置的时候,会调用,实际用的比较少。对应广播的 Action 为 ACTION_APPWIDGET_RESTORED。

onAppWidgetOptionsChanged(): 当小部件布局发生更改的时候调用。对应广播的 Action 为 ACTION_APPWIDGET_OPTIONS_CHANGED。

*后就是 onReceive() 方法了,AppWidgetProvider 重写了该方法,用于分发具体的时间给上述的方法。看看源码:

public void onReceive(Context context, Intent intent) { // Protect against rogue update broadcasts (not really a security issue, // just filter bad broacasts out so subclasses are less likely to crash). String action = intent.getAction(); if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) { Bundle extras = intent.getExtras(); if (extras != null) { int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS); if (appWidgetIds != null && appWidgetIds.length > 0) { this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds); } } } else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) { Bundle extras = intent.getExtras(); if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) { final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID); this.onDeleted(context, new int[] { appWidgetId }); } } else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) { Bundle extras = intent.getExtras(); if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID) && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) { int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID); Bundle widgetExtras = extras.getBundle(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS); this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context), appWidgetId, widgetExtras); } } else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) { this.onEnabled(context); } else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) { this.onDisabled(context); } else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) { Bundle extras = intent.getExtras(); if (extras != null) { int[] oldIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS); int[] newIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS); if (oldIds != null && oldIds.length > 0) { this.onRestored(context, oldIds, newIds); this.onUpdate(context, AppWidgetManager.getInstance(context), newIds); } } } }

AppWidget 练习

下面再自己写个例子,学习 RemoteViews 中的其它知识点,这个例子中小部件布局中用到 button 和 listview。上代码:

小部件的布局文件 mul_app_widget_provider.xml 如下:

```xml` `version``=``"1.0"` `encoding``=``"utf-8"``?>````````````LinearLayout``>````````ListView``>````LinearLayout``>`

小部件的配置信息 mul_app_widget_provider_info.xml 如下:

```xml` `version``=``"1.0"` `encoding``=``"utf-8"``?>``````appwidget-provider``>`

MulAppWidgetProvider.java:

package com.example.joy.remoteviewstest; import android.app.PendingIntent;import android.appwidget.AppWidgetManager;import android.appwidget.AppWidgetProvider;import android.content.ComponentName;import android.content.Context;import android.content.Intent;import android.os.Bundle;import android.text.TextUtils;import android.widget.RemoteViews;import android.widget.Toast; public class MulAppWidgetProvider extends AppWidgetProvider {  public static final String CHANGE_IMAGE = "com.example.joy.action.CHANGE_IMAGE";  private RemoteViews mRemoteViews; private ComponentName mComponentName;  private int[] imgs = new int[]{ R.mipmap.a1, R.mipmap.b2, R.mipmap.c3, R.mipmap.d4, R.mipmap.e5, R.mipmap.f6 };   @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { mRemoteViews = new RemoteViews(context.getPackageName(), R.layout.mul_app_widget_provider); mRemoteViews.setImageViewResource(R.id.iv_test, R.mipmap.ic_launcher); mRemoteViews.setTextViewText(R.id.btn_test, "点击跳转到Activity"); Intent skipIntent = new Intent(context, MainActivity.class); PendingIntent pi = PendingIntent.getActivity(context, 200, skipIntent, PendingIntent.FLAG_CANCEL_CURRENT); mRemoteViews.setOnClickPendingIntent(R.id.btn_test, pi);  // 设置 ListView 的adapter。 // (01) intent: 对应启动 ListViewService(RemoteViewsService) 的intent // (02) setRemoteAdapter: 设置 ListView 的适配器 // 通过setRemoteAdapter将 ListView 和ListViewService关联起来, // 以达到通过 GridWidgetService 更新 gridview 的目的 Intent lvIntent = new Intent(context, ListViewService.class); mRemoteViews.setRemoteAdapter(R.id.lv_test, lvIntent); mRemoteViews.setEmptyView(R.id.lv_test,android.R.id.empty);  // 设置响应 ListView 的intent模板 // 说明:“集合控件(如GridView、ListView、StackView等)”中包含很多子元素,如GridView包含很多格子。 // 它们不能像普通的按钮一样通过 setOnClickPendingIntent 设置点击事件,必须先通过两步。 // (01) 通过 setPendingIntentTemplate 设置 “intent模板”,这是比不可少的! // (02) 然后在处理该“集合控件”的RemoteViewsFactory类的getViewAt()接口中 通过 setOnClickFillInIntent 设置“集合控件的某一项的数据”  /* * setPendingIntentTemplate 设置pendingIntent 模板 * setOnClickFillInIntent 可以将fillInIntent 添加到pendingIntent中 */ Intent toIntent = new Intent(CHANGE_IMAGE); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 200, toIntent, PendingIntent.FLAG_UPDATE_CURRENT); mRemoteViews.setPendingIntentTemplate(R.id.lv_test, pendingIntent);   mComponentName = new ComponentName(context, MulAppWidgetProvider.class); appWidgetManager.updateAppWidget(mComponentName, mRemoteViews); }  @Override public void onReceive(Context context, Intent intent) { super.onReceive(context, intent); if(TextUtils.equals(CHANGE_IMAGE,intent.getAction())){ Bundle extras = intent.getExtras(); int position = extras.getInt(ListViewService.INITENT_DATA); mRemoteViews = new RemoteViews(context.getPackageName(), R.layout.mul_app_widget_provider); mRemoteViews.setImageViewResource(R.id.iv_test, imgs[position]); mComponentName = new ComponentName(context, MulAppWidgetProvider.class); AppWidgetManager.getInstance(context).updateAppWidget(mComponentName, mRemoteViews); } }}

MainActivity.java:

ge com.example.joy.remoteviewstest; import android.support.v7.app.AppCompatActivity;import android.os.Bundle; public class MainActivity extends AppCompatActivity {  @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }}

下面重点是 ListView 在小部件中的用法:

 com.example.joy.remoteviewstest; import android.content.Context;import android.content.Intent;import android.os.Bundle;import android.widget.RemoteViews;import android.widget.RemoteViewsService; import java.util.ArrayList;import java.util.List; public class ListViewService extends RemoteViewsService { public static final String INITENT_DATA = "extra_data";  @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { return new ListRemoteViewsFactory(this.getApplicationContext(), intent); }  private class ListRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {  private Context mContext;  private List mList = new ArrayList<>();  public ListRemoteViewsFactory(Context context, Intent intent) { mContext = context; }  @Override public void onCreate() { mList.add("一"); mList.add("二"); mList.add("三"); mList.add("四"); mList.add("五"); mList.add("六"); }  @Override public void onDataSetChanged() {  }  @Override public void onDestroy() { mList.clear(); }  @Override public int getCount() { return mList.size(); }  @Override public RemoteViews getViewAt(int position) { RemoteViews views = new RemoteViews(mContext.getPackageName(), android.R.layout.simple_list_item_1); views.setTextViewText(android.R.id.text1, "item:" + mList.get(position));  Bundle extras = new Bundle(); extras.putInt(ListViewService.INITENT_DATA, position); Intent changeIntent = new Intent(); changeIntent.setAction(MulAppWidgetProvider.CHANGE_IMAGE); changeIntent.putExtras(extras);  /* android.R.layout.simple_list_item_1 --- id --- text1 * listview的item click:将 changeIntent 发送, * changeIntent 它默认的就有action 是provider中使用 setPendingIntentTemplate 设置的action*/ views.setOnClickFillInIntent(android.R.id.text1, changeIntent); return views; }  /* 在更新界面的时候如果耗时就会显示 正在加载... 的默认字样,但是你可以更改这个界面 * 如果返回null 显示默认界面 * 否则 加载自定义的,返回RemoteViews */ @Override public RemoteViews getLoadingView() { return null; }  @Override public int getViewTypeCount() { return 1; }  @Override public long getItemId(int position) { return position; }  @Override public boolean hasStableIds() { return false; } }}

*后看看清单文件:

```xml` `version``=``"1.0"` `encoding``=``"utf-8"``?>````````````````intent-filter``>````activity``>````````````intent-filter``>``````meta-data``>````receiver``>``````application``>````manifest``>`

这个小部件添加到桌面后有一个 ImageView 显示小机器人,下面有一个 Button ,右边有一个ListView。

这里主要看看,Button 和 ListView 在 RemoteViews中如何使用。、

Button 设置 Text 和 TextView 一样,因为 Button 本身继承自 TextView,Button 设置点击事件如下:

Intent skipIntent = new Intent(context, MainActivity.class); PendingIntent pi = PendingIntent.getActivity(context, 200, skipIntent, PendingIntent.FLAG_CANCEL_CURRENT); mRemoteViews.setOnClickPendingIntent(R.id.btn_test, pi);

用到方法 setOnClickPendingIntent,PendingIntent 表示延迟的 Intent , 与通知中的用法一样。这里点击之后跳转到了 MainActivity。

关于 ListView 的用法就复杂一些了。首先需要自定义一个类继承自 RemoteViewsServices ,并重写 onGetViewFactory 方法,返回 RemoteViewsService.RemoteViewsFactory 接口的对象。这里定义了一个内部类实现该接口,需要重写多个方法,与 ListView 的多布局适配很类似。重点方法是

1

public RemoteViews getViewAt(int position){}

这个方法中指定了 ListView 的每一个 item 的布局以及内容,同时通过 setOnClickFillInIntent() 或者 setOnClickPendingIntent() 给 item 设置点击事件。这里我实现的点击 item,替换左边的 ImageView 的图片。重写了 MulAppWidgetProvider 类的 onReceiver 方法,处理替换图片的逻辑。

程序运行效果如下图:

c269fe94b6bdc604281338b1735b49f3.gif

Android:Dialog显示图片

下看一下效果图
%title插图%num %title插图%num

点击TextView弹出Dialog
点击图片Dialog消失

先看一下MainActivity

package com.cxy.demo;

import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;

import startdialogimageview.qq986945193.dialogdemo.R;

/**
*程序功能:Dialog显示图片
*/
public class MainActivity extends Activity {

Dialog dia;
private TextView tv;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

tv = (TextView) findViewById(R.id.tv);
tv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
dia.show();
}
});

Context context = MainActivity.this;
dia = new Dialog(context, R.style.edit_AlertDialog_style);
dia.setContentView(R.layout.activity_start_dialog);
ImageView imageView = (ImageView) dia.findViewById(R.id.start_img);
imageView.setBackgroundResource(R.mipmap.iv_android);
//选择true的话点击其他地方可以使dialog消失,为false的话不会消失
dia.setCanceledOnTouchOutside(true); // Sets whether this dialog is
Window w = dia.getWindow();
WindowManager.LayoutParams lp = w.getAttributes();
lp.x = 0;
lp.y = 40;
dia.onWindowAttributesChanged(lp);
imageView.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View view) {
dia.dismiss();
}
});
}

}

用到的style

<style name=”edit_AlertDialog_style” parent=”@android:style/Theme.Dialog”>
<item name=”android:windowIsFloating”>true</item>
<item name=”android:windowIsTranslucent”>true</item>
<item name=”android:windowNoTitle”>true</item>
<!– 是否启用标题栏 –>
<item name=”android:windowBackground”>@android:color/transparent</item>
<item name=”android:background”>@android:color/transparent</item>
<item name=”android:backgroundDimEnabled”>true</item>
<!– 是否使用背景半透明 –>
</style>

布局文件的话就不上传了,比较简单。

【Android】TextView设置段落间距

TextView只提供设置行距的方法,没有提供段落间距的方法,但是提供了一个 SpannableString 类来给TextView设置各种效果,
如图:

其中一个给文字替换为图片的效果给我带来了灵感,

我可以用一个图片(*后换成一个宽1px,指定高度的透明长方形,xml中画出来的)来模拟段落间距。

注意画出来的高度,不能使用 用尺子直接量的值,而要比这个高度要小。
为什么呢,我也不清楚,不过我还是要上个图,我估计应该是两行文字之间的line gap的原因。

后台给我们返回的json中段落区分符是“\n”,我们先把”\n”替换为”\n\r”

“\n”用来换行,“\r”就是那个将要被替换的字符了(为什么要用\r呢,阴差阳错啦,后来发现\r挺合适的,也不会重复);*后就是用SpannableString来处理文字了。

/**
* 设置TextView段落间距
*
* @param context 上下文
* @param tv 给谁设置段距,就传谁
* @param content 文字内容
* @param paragraphSpacing 请输入段落间距(单位dp)
* @param lineSpacingExtra xml中设置的的行距(单位dp)
*/
public static void setParagraphSpacing(Context context, TextView tv, String content, int paragraphSpacing, int lineSpacingExtra) {
if (!content.contains(“\n”)) {
tv.setText(content);
return;
}
content = content.replace(“\n”, “\n\r”);

int previousIndex = content.indexOf(“\n\r”);
//记录每个段落开始的index,*段没有,从第二段开始
List nextParagraphBeginIndexes = new ArrayList();
nextParagraphBeginIndexes.add(previousIndex);
while (previousIndex != -1) {
int nextIndex = content.indexOf(“\n\r”, previousIndex + 2);
previousIndex = nextIndex;
if (previousIndex != -1) {
nextParagraphBeginIndexes.add(previousIndex);
}
}
//获取行高(包含文字高度和行距)
float lineHeight = tv.getLineHeight();

//把\r替换成透明长方形(宽:1px,高:字高+段距)
SpannableString spanString = new SpannableString(content);
Drawable d = ContextCompat.getDrawable(context, R.drawable.paragraph_space);
float density = context.getResources().getDisplayMetrics().density;
//int强转部分为:行高 – 行距 + 段距
d.setBounds(0, 0, 1, (int) ((lineHeight – lineSpacingExtra * density) / 1.2 + (paragraphSpacing – lineSpacingExtra) * density));

for (int index : nextParagraphBeginIndexes) {
// \r在String中占一个index
spanString.setSpan(new ImageSpan(d), index + 1, index + 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
tv.setText(spanString);
}

%title插图%num

那个透明的长方形很简单,随手一画就有了。

%title插图%num

下边这个就是替换后的效果,只不过这时候为了展示替换效果,那个长方形还是用了个orange色。

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速