Android:实现一个带动画轮播效果的公告条。

修改历史

2016.01.20
如果多次setNoticeList,可能会出现重复。那是因为setNoticeList的时候没有移除掉runnable,动画重复了的问题。不过也就那几秒钟有问题,等时间过后会自动回复正常。现在已经修复, Demo也已经重新上传了一份。

2016.03.16
1.20号的bug未成功修复,发现这是Android本身View动画的BUG,解决方法是将代码中的invisible改成gone,虽然还是有点小问题,但是已经无伤大雅。Android原生的TextSwitch控件也会出现相同的问题。博文中代码已修改,Demo就不重新上传了,自己修改吧

一、写在前面

很简单的一个小控件,项目刚好有这个需求,没有写的太好,动画能设置,能用就行,欢迎大家一起学习交流。
这是预览图,如果看到卡可能是因为制作成gif的原因。模拟器和真机很流畅。
Android:实现一个带动画轮播效果的公告条。_第1张图片

二、使用方式

自定义属性

    <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

你可能感兴趣的:(Android:实现一个带动画轮播效果的公告条。)