Android:实现一个带动画轮播效果的公告条。
修改历史
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<String> 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<TextView> 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<String> 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
更多相关文章
- Android(安卓)activity启动关闭时滑动出现消失(并解决activity跳
- android矢量动画
- Android(安卓)SlidingMenu 开源项目 侧拉菜单的使用(详细配置)
- Android(安卓)跳转权限设置界面的终极适配(适配各大定制 ROM)
- 基于XML的android property animation
- android studio设置Tab为四空格缩进
- Matrix用法
- 当你的Android(安卓)Studio 设置No proxy不起作用时,该怎么做?
- android 系统权限大全