集成了几种方式的跑马灯效果实现,包括水平方向和垂直方向的跑马灯效果,包括文字也包括图片,闲话不多说,上图,
需要的小伙伴,点击进行下载
主要的自定义View
package com.testanimdemo.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.support.annotation.AnimRes;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.TextView;
import android.widget.ViewFlipper;
import com.testanimdemo.R;
import java.util.ArrayList;
import java.util.List;
/**
* <p>作者 wurui</p>
* <p>时间 2018/11/28 0028</p>
* <p>包名 com.testanimdemo.widget</p>
* <p>描述 </p>
*/
public class VerticalMarqueeView extends ViewFlipper {
private int interval = 3000;
private boolean hasSetAnimDuration = false;
private int animDuration = 1000;
private int textSize = 14;
private int textColor = 0xffffffff;
private boolean singleLine = false;
private int gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
private static final int GRAVITY_LEFT = 0;
private static final int GRAVITY_CENTER = 1;
private static final int GRAVITY_RIGHT = 2;
private boolean hasSetDirection = false;
private int direction = DIRECTION_BOTTOM_TO_TOP;
private static final int DIRECTION_BOTTOM_TO_TOP = 0;
private static final int DIRECTION_TOP_TO_BOTTOM = 1;
private static final int DIRECTION_RIGHT_TO_LEFT = 2;
private static final int DIRECTION_LEFT_TO_RIGHT = 3;
@AnimRes
private int inAnimResId = R.anim.anim_bottom_in;
@AnimRes
private int outAnimResId = R.anim.anim_top_out;
private int position;
private List<? extends CharSequence> notices = new ArrayList<>();
private OnItemClickListener onItemClickListener;
public VerticalMarqueeView(Context context) {
this(context, null);
}
public VerticalMarqueeView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MarqueeViewStyle, defStyleAttr, 0);
interval = typedArray.getInteger(R.styleable.MarqueeViewStyle_mvInterval, interval);
hasSetAnimDuration = typedArray.hasValue(R.styleable.MarqueeViewStyle_mvAnimDuration);
animDuration = typedArray.getInteger(R.styleable.MarqueeViewStyle_mvAnimDuration, animDuration);
singleLine = typedArray.getBoolean(R.styleable.MarqueeViewStyle_mvSingleLine, false);
if (typedArray.hasValue(R.styleable.MarqueeViewStyle_mvTextSize)) {
textSize = (int) typedArray.getDimension(R.styleable.MarqueeViewStyle_mvTextSize, textSize);
textSize = Utils.px2sp(context, textSize);
}
textColor = typedArray.getColor(R.styleable.MarqueeViewStyle_mvTextColor, textColor);
int gravityType = typedArray.getInt(R.styleable.MarqueeViewStyle_mvGravity, GRAVITY_LEFT);
switch (gravityType) {
case GRAVITY_LEFT:
gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
break;
case GRAVITY_CENTER:
gravity = Gravity.CENTER;
break;
case GRAVITY_RIGHT:
gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
break;
}
hasSetDirection = typedArray.hasValue(R.styleable.MarqueeViewStyle_mvDirection);
direction = typedArray.getInt(R.styleable.MarqueeViewStyle_mvDirection, direction);
if (hasSetDirection) {
switch (direction) {
case DIRECTION_BOTTOM_TO_TOP:
inAnimResId = R.anim.anim_bottom_in;
outAnimResId = R.anim.anim_top_out;
break;
case DIRECTION_TOP_TO_BOTTOM:
inAnimResId = R.anim.anim_top_in;
outAnimResId = R.anim.anim_bottom_out;
break;
case DIRECTION_RIGHT_TO_LEFT:
inAnimResId = R.anim.anim_right_in;
outAnimResId = R.anim.anim_left_out;
break;
case DIRECTION_LEFT_TO_RIGHT:
inAnimResId = R.anim.anim_left_in;
outAnimResId = R.anim.anim_right_out;
break;
}
} else {
inAnimResId = R.anim.anim_bottom_in;
outAnimResId = R.anim.anim_top_out;
}
typedArray.recycle();
setFlipInterval(interval);
}
/**
* 根据字符串,启动翻页公告
*
* @param notice 字符串
*/
public void startWithText(String notice) {
startWithText(notice, inAnimResId, outAnimResId);
}
/**
* 根据字符串,启动翻页公告
*
* @param notice 字符串
* @param inAnimResId 进入动画的resID
* @param outAnimResID 离开动画的resID
*/
@SuppressWarnings("deprecation")
public void startWithText(final String notice, final @AnimRes int inAnimResId, final @AnimRes int outAnimResID) {
if (TextUtils.isEmpty(notice)) return;
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
startWithFixedWidth(notice, inAnimResId, outAnimResID);
}
});
}
/**
* 根据字符串和宽度,启动翻页公告
*
* @param notice 字符串
*/
private void startWithFixedWidth(String notice, @AnimRes int inAnimResId, @AnimRes int outAnimResID) {
int noticeLength = notice.length();
int width = Utils.px2dip(getContext(), getWidth());
if (width == 0) {
throw new RuntimeException("Please set the width of MarqueeView !");
}
int limit = width / textSize;
List list = new ArrayList();
if (noticeLength <= limit) {
list.add(notice);
} else {
int size = noticeLength / limit + (noticeLength % limit != 0 ? 1 : 0);
for (int i = 0; i < size; i++) {
int startIndex = i * limit;
int endIndex = ((i + 1) * limit >= noticeLength ? noticeLength : (i + 1) * limit);
list.add(notice.substring(startIndex, endIndex));
}
}
if (notices == null) notices = new ArrayList<>();
notices.clear();
notices.addAll(list);
postStart(inAnimResId, outAnimResID);
}
/**
* 根据字符串列表,启动翻页公告
*
* @param notices 字符串列表
*/
public void startWithList(List<? extends CharSequence> notices) {
startWithList(notices, inAnimResId, outAnimResId);
}
/**
* 根据字符串列表,启动翻页公告
*
* @param notices 字符串列表
* @param inAnimResId 进入动画的resID
* @param outAnimResID 离开动画的resID
*/
public void startWithList(List<? extends CharSequence> notices, @AnimRes int inAnimResId, @AnimRes int outAnimResID) {
if (Utils.isEmpty(notices)) return;
setNotices(notices);
postStart(inAnimResId, outAnimResID);
}
private void postStart(final @AnimRes int inAnimResId, final @AnimRes int outAnimResID) {
post(new Runnable() {
@Override
public void run() {
start(inAnimResId, outAnimResID);
}
});
}
private boolean isAnimStart = false;
private void start(final @AnimRes int inAnimResId, final @AnimRes int outAnimResID) {
removeAllViews();
clearAnimation();
position = 0;
addView(createTextView(notices.get(position)));
if (notices.size() > 1) {
setInAndOutAnimation(inAnimResId, outAnimResID);
startFlipping();
}
if (getInAnimation() != null) {
getInAnimation().setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
if (isAnimStart) {
animation.cancel();
}
isAnimStart = true;
}
@Override
public void onAnimationEnd(Animation animation) {
position++;
if (position >= notices.size()) {
position = 0;
}
View view = createTextView(notices.get(position));
if (view.getParent() == null) {
addView(view);
}
isAnimStart = false;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
}
private TextView createTextView(CharSequence text) {
TextView textView = (TextView) getChildAt((getDisplayedChild() + 1) % 3);
if (textView == null) {
textView = new TextView(getContext());
textView.setGravity(gravity);
textView.setTextColor(textColor);
textView.setTextSize(textSize);
textView.setSingleLine(singleLine);
}
textView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(getPosition(), (TextView) v);
}
}
});
textView.setText(text);
textView.setTag(position);
return textView;
}
public int getPosition() {
return (int) getCurrentView().getTag();
}
public List<? extends CharSequence> getNotices() {
return notices;
}
public void setNotices(List<? extends CharSequence> notices) {
this.notices = notices;
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public interface OnItemClickListener {
void onItemClick(int position, TextView textView);
}
/**
* 设置进入动画和离开动画
*
* @param inAnimResId 进入动画的resID
* @param outAnimResID 离开动画的resID
*/
private void setInAndOutAnimation(@AnimRes int inAnimResId, @AnimRes int outAnimResID) {
Animation inAnim = AnimationUtils.loadAnimation(getContext(), inAnimResId);
if (hasSetAnimDuration) inAnim.setDuration(animDuration);
setInAnimation(inAnim);
Animation outAnim = AnimationUtils.loadAnimation(getContext(), outAnimResID);
if (hasSetAnimDuration) outAnim.setDuration(animDuration);
setOutAnimation(outAnim);
}
}
package com.testanimdemo.widget;
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;
import com.testanimdemo.R;
/**
* <p>作者 wurui</p>
* <p>时间 2018/11/28 0028</p>
* <p>包名 com.testanimdemo.widget</p>
* <p>描述 水平滚动的跑马灯效果</p>
*/
public class MarqueeView extends HorizontalScrollView implements Runnable{
private Context context;
private LinearLayout mainLayout;//跑马灯滚动部分
private View parentView;//自定义父类滚动容器
private boolean hasParent=false;//默认没有父容器
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();
if(hasParent){
this.addView(parentView);
}else{
mainLayout = (LinearLayout)LayoutInflater.from(context).inflate(R.layout.scroll_content, null);
this.addView(mainLayout);
}
}
/**
* 添加自定义滚动视图
* @param parentView
*/
public void setParentView(View parentView){
this.parentView=parentView;
hasParent=true;
}
/**
* 添加单个视图 如TextView imageView等等
* @param view
*/
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);
if(!hasParent){
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:
if(hasParent){
parentView.scrollTo(currentX, 0);
}else{
mainLayout.scrollTo(currentX, 0);
}
currentX --;
if (-currentX >= screenWidth) {
if(hasParent){
parentView.scrollTo(viewWidth, 0);
}else{
mainLayout.scrollTo(viewWidth, 0);
}
currentX = viewWidth;
}
break;
case RIGHT_TO_LEFT:
if(hasParent){
parentView.scrollTo(currentX, 0);
}else{
mainLayout.scrollTo(currentX, 0);
}
currentX ++;
if (currentX >= viewWidth) {
if(hasParent){
parentView.scrollTo(-screenWidth, 0);
}else{
mainLayout.scrollTo(-screenWidth, 0);
}
currentX = -screenWidth;
}
break;
default:
break;
}
postDelayed(this, 50 / scrollSpeed);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return false;
}
}