纵向滚动通知 垂直跑马灯
自定义控件ScrollNotification(extends ViewFlipper)
ViewFlipper官方解释:Simple ViewAnimator that will animate between two or more views that have been added to it. Only one child is shown at a time. If requested, can automatically flip between each child at a regular interval. 是一个简单的ViewAnimator,可以使添加进去的views动起来,一次只展示一个子view,可以设置固定时间间隔实现每个子view之间的自动切换。
实现思路:1.根据list向ViewFliper动态添加TextView作为子view;2.利用ViewFliper的setInAnimation()、setOutAnimation()指定进入进出动画来实现垂直滚动效果。
效果:
1.自定义xml属性(Values/attrs.xml)
问题描述:允许通过layout布局文件直接指定滚动通知内tTextView的TextSize、TextColor、SingleLine、Gravity属性,但是ViewFlipper没有上述属性,需要自定义xml属性。
实现方案:在项目values文件夹下创建attrs.xml文件(文件名随意),根标签为resources,styleable名字为ScrollNotification(一般使用控件名称)。在这个名字里可以定义多个属性,例如定义了名为mvTextSize的属性,格式为dimension|reference,意为只能使用sp、dp、px、或引用R.dimens文件。其他自定义属性类似。
<resources>
<declare-styleable name="ScrollNotification">
<attr name="mvInterval" format="integer|reference"/>
<attr name="mvTextSize" format="dimension|reference"/>
<attr name="mvTextColor" format="color|reference"/>
<attr name="mvSingleLine" format="boolean"/>
<attr name="mvGravity">
<enum name="left" value="0"/>
<enum name="center" value="1"/>
<enum name="right" value="2"/>
</attr>
</declare-styleable>
</resources>
这里的自定义属性的format,可以有很多种:
- reference:attr的值只能指向某一资源的ID,例如取值@id/textView。
- string:表示attr是字符串类型。
- color:attr是颜色类型,例如#ff0000,也可以使用一个指向Color的资源,比如@android:color/background_dark,但是不能用0xffff0000这样的值。
- dimension:attr是尺寸类型,例如取值16px、16dp,也可以使用一个指向类型的资源,比如@android:dimen/app_icon_size。
- boolean:表示attr是布尔类型的值,取值只能是true或false。
- integer:表示attr是整数类型,取值只能是整数,不能是浮点数。
- float:attr是浮点数类型,取值只能是浮点数或整数。
- fraction:表示attr是百分数类型,取值只能以%结尾,例如30%、120.5%等。
- enum:表示attr是枚举类型,在定义enum类型的attr时,可以将attr的format设置为enum,也可以不用设置attr的format属性,但是必须在attr节点下面添加一个或多个enum节点,这样取值就只能去enum里定义的值了。
- flag:表示attr是bit位标记,flag与enum有相似之处,定义了flag的attr,在设置值时,可以通过|设置多个值,而且每个值都对应一个bit位,这样通过按位或操作符|可以将多个值合成一个值,一般在用flag表示某个字段支持多个特性。使用flag类型时,不要设置attr的format的属性,直接在attr节点下面添加flag节点。
2.在layout布局文件中使用
- 1.在根标签中添加新的命名空间
为什么要添加新的命名空间:观察一下layout布局文件中各个属性是以android:开头的,例如android:layout_width=”match_parent”,android:是命名空间,根标签最外层有一个xmlns:android=”http://schemas.android.com/apk/res/android”,xmlns(xmlNameSpace)的前缀为android,也就是说所有以android:开头的属性都是在xmlns:android=”http://schemas.android.com/apk/res/android”命名空间之下的。要使用在步骤1中的自定义属性,需要一个命名空间:
<1>app为任意名称,看个人意愿,使用app为命名空间时,直接在控件里使用自定义属性,eclipse会自动添加该命名空间;
<2>http://schemas.android.com/apk/res/是固定的;
<3>com.tiddler.testdemo为当前应用包名。在eclipse环境下,可以使用当前应用包名;但是在Android Studio中因为使用Gradle进行build,而Gradle不允许自定义的命名空间以包名结尾,在Android Studio中可以这样定义命名空间xmlns:app=”http://schemas.android.com/apk/res-auto”,这样定义的命名空间自动指向当前App的命名空间。):
- 2.在layout布局文件中使用自定义控件,并使用自定义属性
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.tiddler.demo"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#cc18A15F"
android:orientation="horizontal"
android:paddingLeft="10dp"
android:paddingRight="10dp" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
<com.tiddler.myview.ScrollNotification
android:id="@+id/scrollNotification"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:flipInterval="2000"
app:mvSingleLine="true"
app:mvTextColor="#FFFFFF"
app:mvTextSize="8sp"
app:mvGravity="left"/>
</LinearLayout>
</RelativeLayout>
3.自定义ScrollNotification.java类
- 1.声明成员变量-数据列表,并实现get()、set()方法,以便使用代码指定数据
private Context mContext;
private List<String> notices;// 数据列表
public List<String> getNotices() {
return notices;
}
public void setNotices(List<String> notices) {
this.notices = notices;
}
- 2.创建init()方法,将layout文件中的属性值读出来,赋给成员变量
// 声明各个自定义属性对应的成员变量,并设置默认值
private int textSize = 6;// text字体大小
private int textColor = 0x000000;// text颜色
private boolean singleLine = false;// 是否单行显示
private int gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
private static final int TEXT_GRAVITY_LEFT = 0, TEXT_GRAVITY_CENTER = 1,
TEXT_GRAVITY_RIGHT = 2;
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
this.mContext = context;
if (notices == null) {
notices = new ArrayList<>();
}
//通过Context的obtainStyledAttributes方法获取TypedArray对象
/*
* 其中,TypedArray实例是个属性的容器。AttributeSet是节点的属性集合,在本例中是<com.tiddler.testdemo
* .myview.ScrollNotificationa节点中的属性集合。
*/
TypedArray typedArray = getContext().obtainStyledAttributes(attrs,
R.styleable.ScrollNotification, defStyleAttr, 0);
//在程序中获取Layout文件中各个自定义属性的赋值,如果没有,则使用默认的值。
singleLine = typedArray.getBoolean(
R.styleable.ScrollNotification_mvSingleLine, false);
if (typedArray.hasValue(R.styleable.ScrollNotification_mvTextSize)) {
/*
* getDimension如果是dimen是dp或sp的单位,将其乘以density,如果是px,则不乘,返回类型为float。
* getDimensionPixelSize总体类似,但是当单位是px时,也会乘以density,返回类型为int。
*/
textSize = (int) typedArray.getDimensionPixelSize(
R.styleable.ScrollNotification_mvTextSize, textSize);
}
textColor = typedArray.getColor(
R.styleable.ScrollNotification_mvTextColor, textColor);
int gravityType = typedArray.getInt(
R.styleable.ScrollNotification_mvGravity, TEXT_GRAVITY_LEFT);
switch (gravityType) {
case TEXT_GRAVITY_CENTER:
gravity = Gravity.CENTER;
break;
case TEXT_GRAVITY_RIGHT:
gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
break;
}
//在使用完typedArray之后,要调用recycle方法回收资源
typedArray.recycle();
//设置动画
setInAnimation(mContext, R.anim.in_scroll_notification);
setOutAnimation(mContext, R.anim.out_scroll_notification);
}
in_scroll_notification.xml文件
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="500"
android:fromYDelta="100.0%p"
android:toYDelta="0.0" />
<alpha
android:duration="500"
android:fromAlpha="0.0"
android:toAlpha="1.0" />
</set>
out_scroll_notification.xml文件
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="500"
android:fromYDelta="0.0"
android:toYDelta="-100.0%p" />
<alpha
android:duration="500"
android:fromAlpha="1.0"
android:toAlpha="0.0" />
</set>
- 3.在构造函数中调用init()方法
public ScrollNotification(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
-4.创建createTextView(String text, int position),并将自定义属性里设置的值设置到TextView
/**
* 创建ViewFlipper下的TextView
*/
private TextView createTextView(String text, int position) {
TextView tv = new TextView(mContext);
tv.setGravity(gravity);
tv.setText(text);
tv.setTextColor(textColor);
tv.setTextSize(textSize);
tv.setSingleLine(singleLine);
tv.setTag(position);
return tv;
}
- 5.创建start(),并为子View添加点击事件
private OnItemClickListener onItemClickListener;
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public interface OnItemClickListener {
void onItemClick(int position, TextView textView);
}
/** 启动轮播 */
public boolean start() {
if (notices == null || notices.size() == 0){
return false;
}
removeAllViews();
for (int i = 0; i < notices.size(); i++) {
final TextView textView = createTextView(notices.get(i), i);
final int finalI = i;
textView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(finalI, textView);
}
}
});
addView(textView);
}
if (notices.size() > 1) {
startFlipping();
}
return true;
}
- 6.创建startWithList(List notices)用以在ui线程调用
// 根据公告字符串列表启动轮播
public void startWithList(List<String> notices) {
setNotices(notices);
start();
}
4.在Activity中使用ScrollNotification
private ScrollNotification mScrollNotification;
List<String> notices = new ArrayList<>();
notices.add("测试数据11111111");
notices.add("测试数据22222222");
notices.add("测试数据33333333");
notices.add("测试数据4444444");
notices.add("测试数据5555555");
notices.add("测试数据6666666");
mScrollNotification = (ScrollNotification) findViewById(R.id.scrollNotification);
mScrollNotification.startWithList(notices);
mScrollNotification.setOnItemClickListener(new ScrollNotification.OnItemClickListener() {
@Override
public void onItemClick(int position, TextView textView) {
// TODO Auto-generated method stub
Toast.makeText(getApplicationContext(), textView.getText().toString(), 0).show();
}
});