效果图:
(截取的gif最小间隔50ms,所以看起来有点卡,实际还是很流畅的~)
说到跑马灯效果,最先想到的应该就是使用TextView自带的android:ellipsize="marquee"属性来实现。但是这个属性在简单易用的同时也有很大的局限性,例如由于ellipsize属性是只有当文字超出控件长度时才会生效,所以当文字较短时就无法产生跑马灯的效果。再比如不能指定滚动方向、不能指定滚动速度等。在开发中常常会遇到复杂的跑马灯需求用自带的属性无法实现的情况,之前接到一个需求要做文字的跑马灯,可是文字一共就只有4个字...那么就无法使用TextView自带的属性实现了,于是就自己实现了一个高度定制化的跑马灯View,可以实现包括图片跑马灯效果、TextView文字不超过宽度时的跑马灯效果等任意View的跑马灯效果,并且可以指定滚动的方向和速度。
主要实现方式是继承HorizontalScrollView并实现Runnable接口,通过scrollTo方法刷新界面实现。以下是MarqueeView源码:
package xyy.marqueeview;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
/**
* 跑马灯View
* Created by xuyy on 2016/8/23.
*/
public class MarqueeView extends HorizontalScrollView implements Runnable{
private Context context;
private LinearLayout mainLayout;//跑马灯滚动部分
private int scrollSpeed = 5;//滚动速度
private int scrollDirection = LEFT_TO_RIGHT;//滚动方向
private int currentX;//当前x坐标
private int viewMargin = 20;//View间距
private int viewWidth;//View总宽度
private int screenWidth;//屏幕宽度
public static final int LEFT_TO_RIGHT = 1;
public static final int RIGHT_TO_LEFT = 2;
public MarqueeView(Context context) {
this(context, null);
}
public MarqueeView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MarqueeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
initView();
}
void initView() {
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
screenWidth = wm.getDefaultDisplay().getWidth();
mainLayout = (LinearLayout)LayoutInflater.from(context).inflate(R.layout.scroll_content, null);
this.addView(mainLayout);
}
public void addViewInQueue(View view){
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
lp.setMargins(viewMargin, 0, 0, 0);
view.setLayoutParams(lp);
mainLayout.addView(view);
view.measure(0, 0);//测量view
viewWidth = viewWidth + view.getMeasuredWidth() + viewMargin;
}
//开始滚动
public void startScroll(){
removeCallbacks(this);
currentX = (scrollDirection == LEFT_TO_RIGHT ? viewWidth : -screenWidth);
post(this);
}
//停止滚动
public void stopScroll(){
removeCallbacks(this);
}
//设置View间距
public void setViewMargin(int viewMargin){
this.viewMargin = viewMargin;
}
//设置滚动速度
public void setScrollSpeed(int scrollSpeed){
this.scrollSpeed = scrollSpeed;
}
//设置滚动方向 默认从左向右
public void setScrollDirection(int scrollDirection){
this.scrollDirection = scrollDirection;
}
@Override
public void run() {
switch (scrollDirection){
case LEFT_TO_RIGHT:
mainLayout.scrollTo(currentX, 0);
currentX --;
if (-currentX >= screenWidth) {
mainLayout.scrollTo(viewWidth, 0);
currentX = viewWidth;
}
break;
case RIGHT_TO_LEFT:
mainLayout.scrollTo(currentX, 0);
currentX ++;
if (currentX >= viewWidth) {
mainLayout.scrollTo(-screenWidth, 0);
currentX = -screenWidth;
}
break;
default:
break;
}
postDelayed(this, 50 / scrollSpeed);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return false;
}
}
addViewInQueue()方法中先将添加的View加到滚动部分mainLayout中,再计算添加View的总宽度,便于下面计算滚动起点。
startScroll()方法中,如果方向是从左向右,那就将滚动起点设为(viewWidth, 0),如果是从右向左,那就将滚动起点设为(-screenWidth, 0)。这里要注意的是scrollTo方法参数为正时在坐标系中是负向(向左)的,参数为负时在坐标系中是正向(向右)的。
mainLayout就是滚动部分了。当mainLayout整体全部滚动出屏幕,即滚动距离=screenWidth+viewWidth时重置起点,实现循环跑马灯的效果。
mainLayout的布局很简单,就是一个横向的LinearLayout。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
</LinearLayout>
使用:
public class MainActivity extends Activity {
private MarqueeView marqueeView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
marqueeView = (MarqueeView) findViewById(R.id.marquee_view);
initMarqueeView();
}
private void initMarqueeView(){
ImageView iv1 = new ImageView(this);
iv1.setImageResource(R.drawable.pic1);
marqueeView.addViewInQueue(iv1);
ImageView iv2 = new ImageView(this);
iv2.setImageResource(R.drawable.pic2);
marqueeView.addViewInQueue(iv2);
ImageView iv3 = new ImageView(this);
iv3.setImageResource(R.drawable.pic3);
marqueeView.addViewInQueue(iv3);
ImageView iv4 = new ImageView(this);
iv4.setImageResource(R.drawable.pic4);
marqueeView.addViewInQueue(iv4);
ImageView iv5 = new ImageView(this);
iv5.setImageResource(R.drawable.pic5);
marqueeView.addViewInQueue(iv5);
ImageView iv6 = new ImageView(this);
iv6.setImageResource(R.drawable.pic6);
marqueeView.addViewInQueue(iv6);
marqueeView.setScrollSpeed(8);
marqueeView.setScrollDirection(MarqueeView.RIGHT_TO_LEFT);
marqueeView.setViewMargin(15);
marqueeView.startScroll();
}
}
MainActivity布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<xyy.marqueeview.MarqueeView
android:id="@+id/marquee_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="@color/black60"
android:fillViewport="true"
android:gravity="center"
android:padding="5dp"/>
</RelativeLayout>
这里用的是ImageView,当然替换成其他任何View都是可以的~
网友评论
if (currentX >= viewWidth) {
mainLayout.scrollTo(-screenWidth, 0);
currentX = -screenWidth;
}
* 移除 view
* @param view
*/
public void removeViewInQueue(View view){
mainLayout.removeView(view);
}