2016.01.20
如果多次setNoticeList,可能会出现重复。那是因为setNoticeList的时候没有移除掉runnable,动画重复了的问题。不过也就那几秒钟有问题,等时间过后会自动回复正常。现在已经修复, Demo也已经重新上传了一份。
2016.03.16
1.20号的bug未成功修复,发现这是Android本身View动画的BUG,解决方法是将代码中的invisible改成gone,虽然还是有点小问题,但是已经无伤大雅。Android原生的TextSwitch控件也会出现相同的问题。博文中代码已修改,Demo就不重新上传了,自己修改吧
很简单的一个小控件,项目刚好有这个需求,没有写的太好,动画能设置,能用就行,欢迎大家一起学习交流。
这是预览图,如果看到卡可能是因为制作成gif的原因。模拟器和真机很流畅。
自定义属性
<declare-styleable name="NoticeView">
<attr name="noticeTextSize" format="dimension"/>
<attr name="noticeTextColor" format="color|reference"/>
declare-styleable>
布局文件中,随意使用。设置字体和颜色要自定义属性。
<com.aitsuki.balllayout.NoticeView
android:id="@+id/notice_view"
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:layout_gravity="center"
android:background="#4f00"
app:textSize="30dp"
app:textColor="#000">
com.aitsuki.balllayout.NoticeView>
最后,在Activity中可以这么使用
public class MainActivity extends AppCompatActivity {
private NoticeView notice_view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 首先,模拟一个公告的集合。需要字符串泛型的list
final List list = new ArrayList<>();
list.add("推荐歌曲:Eyelis - 絆にのせて");
list.add("挺好听的,我听了快100遍了");
list.add("好想回宿舍打游戏=。=");
// 然后设置进去。
notice_view = (NoticeView) findViewById(R.id.notice_view);
notice_view.setNoticeList(list);
// 这里可以设置一个动画集合,如果不想要动画可以设置成null
// 不过这里设置动画我设计的不太友好,需要的直接改源码可能更快捷。
// notice_view.setEnterAnimation(null);
// notice_view.setExitAnimation(null);
// 默认动画效果就是渐变和位移,可以通过这个设置动画的时长,默认是1000
// notice_view.setAnimationDuration(1000);
// 公告切换一次是3秒,可以通过这个方法设置,设置的比动画的长就好。默认是3000
// notice_view.setNoticeDuration(2000);
// 这里就是监听点击事件,TextView是点中的那个公告,position是位置。
// 比如点击之后想该条公告边灰色,就可以view.setTextColor();实现了
notice_view.setOnItemClickListener(new NoticeView.OnItemClickListener() {
@Override
public void onItemClick(TextView view, int position) {
String s = list.get(position);
view.setTextColor(Color.GRAY);
Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show();
}
});
}
// 我们可以在这两个生命周期中启动和暂停公告的轮播,因为很多时候,公告可以点进其他Activity
// 我希望回到当前页面的时候,还可以看到这条公告在当前。
// 而且在其他页面的时候,这里没必要耗费资源去做动画。
@Override
protected void onResume() {
super.onResume();
notice_view.start();
}
@Override
protected void onPause() {
super.onPause();
notice_view.pause();
}
}
思路:
建一个类继承FrameLayout
1. 传入一个字符串集合,遍历字符串集合新建TextView。
2. 然后将TextView依次添加进FrameLayout
3. 控制TextView显示隐藏并做补间动画
以下是完整代码
package com.aitsuki.noticeviewdemo;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Looper;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.AnimationSet;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
/**
* Created by AItsuki on 2016/1/18.
*/
public class NoticeView extends FrameLayout implements View.OnClickListener {
private final long DEFAULT_ANIMATION_DURATION = 1000; // 动画时长
private final long DEFAULT_NOTICE_SPACE = 3000; // 公告切换时长
private final int DEFAULT_TEXT_COLOR = 0xff000000; // 默认字体颜色
private long mNoticeDuration = DEFAULT_NOTICE_SPACE;
private int mTextColor = DEFAULT_TEXT_COLOR;
private float mTextSize;
private LayoutParams mLayoutParams;
private List mNoticeList;
private int mCurrentNotice;
private AnimationSet mEnterAnimSet;
private AnimationSet mExitAnimSet;
private Handler mHandler = new Handler(Looper.getMainLooper());
private NoticeRunnable mNoticeRunnalbe;
private OnItemClickListener mListener;
private TextPaint textPaint;
private boolean mIsRunning; // 是否正已经start()
public NoticeView(Context context) {
this(context, null);
}
public NoticeView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.NoticeView);
mTextColor = array.getColor(R.styleable.NoticeView_textColor, DEFAULT_TEXT_COLOR);
mTextSize = array.getDimension(R.styleable.NoticeView_textSize, mTextSize);
array.recycle();
// 初始化动画
createExitAnimation();
createEnterAnimation();
// 初始化一个画笔,用于测量高度
textPaint = new TextPaint();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 如果是未指定大小,那么设置宽为300px
int exceptWidth = 300;
int exceptHeight = 0;
// 计算高度,如果将高度设置为textSize会很丑,因为文字有默认的上下边距。
if(MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
if(mTextSize > 0) {
textPaint.setTextSize(mTextSize);
Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
exceptHeight = (int) (fontMetrics.bottom - fontMetrics.top);
}
}
int width = resolveSize(exceptWidth, widthMeasureSpec);
int height = resolveSize(exceptHeight, heightMeasureSpec);
setMeasuredDimension(width, height);
}
private void createEnterAnimation() {
mEnterAnimSet = new AnimationSet(false);
TranslateAnimation translateAnimation =
new TranslateAnimation(0,0,0,0, TranslateAnimation.RELATIVE_TO_PARENT, 1f,
TranslateAnimation.RELATIVE_TO_SELF, 0f);
AlphaAnimation alphaAnimation = new AlphaAnimation(0f,1f);
mEnterAnimSet.addAnimation(translateAnimation);
mEnterAnimSet.addAnimation(alphaAnimation);
mEnterAnimSet.setDuration(DEFAULT_ANIMATION_DURATION);
}
private void createExitAnimation() {
mExitAnimSet = new AnimationSet(false);
TranslateAnimation translateAnimation =
new TranslateAnimation(0,0,0,0, TranslateAnimation.RELATIVE_TO_SELF, 0f,
TranslateAnimation.RELATIVE_TO_PARENT, -1f);
AlphaAnimation alphaAnimation = new AlphaAnimation(1f,0f);
mExitAnimSet.addAnimation(translateAnimation);
mExitAnimSet.addAnimation(alphaAnimation);
mExitAnimSet.setDuration(DEFAULT_ANIMATION_DURATION);
}
/**
* 设置公告的集合
* @param list
*/
public void setNoticeList(List list) {
// 设置集合的时候,要将上一次的集合清除。
if(list == null || list.size() ==0) {
return;
}
// 暂停轮播
pause();
// 移除所有公告
removeAllViews();
if(mNoticeList == null) {
mNoticeList = new ArrayList<>();
}
mNoticeList.clear();
// 创建TextView
for (int i=0; i< list.size(); i++) {
TextView textView = createTextView(list.get(i));
mNoticeList.add(textView);
addView(textView);
}
// 显示第一条公告
mCurrentNotice = 0;
mNoticeList.get(mCurrentNotice).setVisibility(VISIBLE);
// 启动轮播
start();
}
/**
* 设置条目点击侦听
* @param listener
*/
public void setOnItemClickListener(OnItemClickListener listener) {
setOnClickListener(this);
mListener = listener;
}
/**
* 设置公告的切换间隔
* @param duration
*/
public void setNoticeDuration(long duration) {
if(duration > 0) {
mNoticeDuration = duration;
}
}
/**
* 设置默认动画的时长
* @param duration
*/
public void setAnimationDuration(long duration) {
if(duration > 0) {
if(mEnterAnimSet != null) {
mEnterAnimSet.setDuration(duration);
}
if(mExitAnimSet != null) {
mExitAnimSet.setDuration(duration);
}
}
}
/**
* @param animation
*/
public void setEnterAnimation(AnimationSet animation) {
mEnterAnimSet = animation;
}
/**
* 设置公告的退出动画
* @param animation
*/
public void setExitAnimation(AnimationSet animation) {
mExitAnimSet = animation;
}
/**
* 开始循环播放公告
* 推荐和pause()配合在生命周期中使用
*/
public void start() {
// 如果轮播正在运行中,不重复执行
if(mIsRunning) {
return;
}
if(mNoticeRunnalbe == null) {
mNoticeRunnalbe = new NoticeRunnable();
} else {
mHandler.removeCallbacks(mNoticeRunnalbe);
}
mHandler.postDelayed(mNoticeRunnalbe, mNoticeDuration);
mIsRunning = true;
}
/**
* 暂停循环播放公告
* 推荐和start()配合在生命周期中使用
*/
public void pause() {
// 如果轮播已经停止,不重复执行
if(!mIsRunning) {
return;
}
if(mNoticeRunnalbe!= null) {
mHandler.removeCallbacks(mNoticeRunnalbe);
}
mIsRunning = false;
}
/**
* 当前是否正在轮播公告
* @return
*/
public boolean isRunning() {
return mIsRunning;
}
/**
* TextView默认水平居中, singline, Gone
* @param text
* @return
*/
private TextView createTextView(String text) {
if (mLayoutParams == null) {
mLayoutParams = new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
mLayoutParams.gravity = Gravity.CENTER_VERTICAL;
}
TextView textView = new TextView(getContext());
textView.setLayoutParams(mLayoutParams);
textView.setSingleLine();
textView.setEllipsize(TextUtils.TruncateAt.END);
textView.setTextColor(mTextColor);
textView.setVisibility(GONE);
textView.setText(text);
// 如果有设置字体大小,如果字体大小为null。
if (mTextSize > 0) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,mTextSize);
}
return textView;
}
@Override
public void onClick(View v) {
if(mListener != null && mNoticeList!= null && mNoticeList.size() >0) {
mListener.onItemClick(mNoticeList.get(mCurrentNotice),mCurrentNotice);
}
}
/**
*
* 动画开始的一瞬间,就代表这个公告已经invisiable,下一个公告开始进入,点击事件也是给了下一个公告。
*/
class NoticeRunnable implements Runnable {
@Override
public void run() {
// 隐藏当前的textView
TextView currentView = mNoticeList.get(mCurrentNotice);
currentView.setVisibility(GONE);
if(mExitAnimSet != null) {
currentView.startAnimation(mExitAnimSet);
}
mCurrentNotice++;
if(mCurrentNotice >= mNoticeList.size()) {
mCurrentNotice = 0;
}
// 显示下一个TextView
TextView nextView = mNoticeList.get(mCurrentNotice);
nextView.setVisibility(VISIBLE);
if(mEnterAnimSet != null) {
nextView.startAnimation(mEnterAnimSet);
}
mHandler.postDelayed(this, mNoticeDuration);
}
}
/**
* 点击的回调。TextView是被点中的公告。
*/
public interface OnItemClickListener {
void onItemClick(TextView view, int position);
}
}
非常简单的控件,适合初学者学习_(:з」∠)_
不过这种实现方式不好控制动画,并且补间动画的效果也比较一般。博主表示思考不到3秒就着手写了,所以也就这样了,如果想要更炫酷的动画可以换成属性动画。
话说csdn上传资源后就无法编辑和删除了,只能再上传一份,以后我还是上传到github吧。
这里是Demo下载地址:http://download.csdn.net/detail/u010386612/9411357