首页 > 代码库 > 向上滚动公告栏

向上滚动公告栏

向上滚动公告栏

项目中需要用到类似公告栏的控件,能用的基本不支持多行显示,于是只好自己动手,苦于没有自定义过一个像样的控件,借鉴Android公告条demo,实现了多行向上滚动的控件。在原控件基础之上添加如下功能:

  • 传入数据分页显示
  • 添加Left Drawable
  • 手指触摸事件处理
  • 添加3D动画翻滚效果

效果图

技术分享

源码

package com.android.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Camera;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
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.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.Transformation;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.sd2w.market.client.R;

import java.util.ArrayList;
import java.util.List;

import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;

/**
 * 公告滚动区
 *
 * @author 祁连山
 * @version 1.0
 * @date 2016-08-17
 */

public class RollingView extends FrameLayout implements OnClickListener {

    // 默认动画执行时间
    private static final int ANIMATION_DURATION = 1000;

    // 延迟滚动时间间隔
    private long mDuration = 3000;
    // 字体颜色
    private int mTextColor = 0xff000000;
    // 点击后字体颜色
    private int mClickColor = 0xff0099ff;
    // 字体大小
    private float mTextSize = 14;
    // 行间距
    private int mTextPadding = 10;
    // 画笔
    private Paint mPaint;
    // 默认每页信息数
    private int mPageSize = 3;
    // 最后一页余数
    private int mUpLimited = mPageSize;
    // 当前显示页码
    private int mCurrentPage = 0;
    // 总分页数
    private int mPageCount;
    // 左图片
    private int mLeftDrawable;
    // 分页数据对象
    private List<LinearLayout> mRollingPages;
    // 默认动画
    private AnimationSet mEnterAnimSet;
    private AnimationSet mExitAnimSet;
    private RollingRunnable mRunnable;
    private Handler mHandler;
    private onItemClickListener mClickListener;
    // 布局参数
    private LayoutParams mFrameParams;
    private LinearLayout.LayoutParams mLinearParams;
    //mEnterDownAnim,mOutUp分别构成向下翻页的进出动画
    private Rotate3dAnimation mEnterDownAnim;
    private Rotate3dAnimation mExitUpAnim;

    //mEnterUpAnim,mOutDown分别构成向下翻页的进出动画
    private Rotate3dAnimation mEnterUpAnim;
    private Rotate3dAnimation mExitDownAnim;

    public RollingView(Context context) {
        this(context, null);
    }

    public RollingView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 从xml中获取属性
        TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.RollingView);
        mTextSize = array.getDimension(R.styleable.RollingView_textSize, mTextSize);
        mTextColor = array.getColor(R.styleable.RollingView_textColor, mTextColor);
        array.recycle();
        // 创建默认显示隐藏动画
        createEnterAnimation();
        createExitAnimation();
        // 初始化画笔
        mPaint = new TextPaint();
        // 初始化Handler对象
        mHandler = new Handler(Looper.getMainLooper());

        mEnterDownAnim = createAnim(-90, 0, true, true);
        mExitUpAnim = createAnim(0, 90, false, true);
        mEnterUpAnim = createAnim(90, 0, true, false);
        mExitDownAnim = createAnim(0, -90, false, false);
    }

    private Rotate3dAnimation createAnim(float start, float end, boolean turnIn, boolean turnUp) {
        final Rotate3dAnimation rotation = new Rotate3dAnimation(start, end, turnIn, turnUp);
        rotation.setDuration(300);
        rotation.setFillAfter(false);
        rotation.setInterpolator(new AccelerateInterpolator());
        return rotation;
    }

    /**
     * 设置分页大小
     *
     * @param pageSize
     */
    public void setPageSize(int pageSize) {
        this.mPageSize = this.mUpLimited = pageSize;
    }

    /**
     * 设置延迟时间
     *
     * @param millionSeconds
     */
    public void setDelayedDuration(long millionSeconds) {
        this.mDuration = millionSeconds;
    }

    /**
     * 设置显示动画
     *
     * @param animation
     */
    public void setEnterAnimation(AnimationSet animation) {
        mEnterAnimSet = animation;
    }

    /**
     * 设置隐藏动画
     *
     * @param animation
     */
    public void setExitAnimation(AnimationSet animation) {
        mExitAnimSet = animation;
    }

    /**
     * 设置行距
     *
     * @param padding
     */
    public void setTextPadding(int padding) {
        this.mTextPadding = padding;
    }

    /**
     * 设置点击后字体颜色
     *
     * @param color
     */
    public void setClickColor(int color) {
        this.mClickColor = color;
    }

    /**
     * 设置左图片
     *
     * @param drawable
     */
    public void setLeftDrawable(int drawable) {
        this.mLeftDrawable = drawable;
    }

    /**
     * 设置点击事件
     *
     * @param clickListener
     */
    public void setOnItemClickListener(onItemClickListener clickListener) {
        if (null == clickListener) return;
        this.mClickListener = clickListener;
    }

    @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) {
                mPaint.setTextSize(mTextSize);
                Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
                exceptHeight = (int) (fontMetrics.bottom - fontMetrics.top);
            }
        }
        int width = resolveSize(exceptWidth, widthMeasureSpec);
        int height = resolveSize(exceptHeight, heightMeasureSpec);
        setMeasuredDimension(width, height);
    }

    public void setRollingText(List<String> array) {
        if (null == array || array.isEmpty()) return;
        this.removeAllViews();
        if (mRollingPages == null) {
            mRollingPages = new ArrayList<>();
        }
        mRollingPages.clear();
        // 计算商数
        int quotient = array.size() / mPageSize;
        // 计算余数
        int remainder = array.size() % mPageSize;
        // 计算需要创建多少页
        mPageCount = remainder == 0 ? quotient : quotient + 1;
        for (int i = 0; i < mPageCount; i++) {
            // 创建一个布局
            LinearLayout container = createContainer();
            if (i == mPageCount - 1) {
                mUpLimited = remainder == 0 ? mPageSize : remainder;
            }
            for (int n = 0; n < mUpLimited; n++) {
                TextView textView = createTextView(array.get(mPageSize * i + n));
                container.addView(textView);
            }
            // 添加到分页中
            mRollingPages.add(container);
            this.addView(container);
        }
        // 初始化显示第一页
        mCurrentPage = 0;
        mRollingPages.get(mCurrentPage).setVisibility(VISIBLE);
        this.setVisibility(mRollingPages.get(mCurrentPage));
    }

    /**
     * 创建页对象
     *
     * @return
     */
    private LinearLayout createContainer() {
        if (mFrameParams == null) {
            mFrameParams = new LayoutParams(MATCH_PARENT, WRAP_CONTENT);
            mFrameParams.gravity = Gravity.CENTER_VERTICAL;
        }
        LinearLayout container = new LinearLayout(getContext());
        container.setLayoutParams(mFrameParams);
        container.setOrientation(LinearLayout.VERTICAL);
        return container;
    }

    private void setVisibility(LinearLayout container) {
        int count = container.getChildCount();
        for (int i = 0; i < count; i++) {
            container.getChildAt(i).setVisibility(VISIBLE);
        }
    }

    /**
     * 创建页内容对象
     *
     * @param text
     * @return
     */
    private TextView createTextView(String text) {
        if (mLinearParams == null) {
            mLinearParams = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
            mLinearParams.gravity = Gravity.CENTER_VERTICAL;
        }
        TextView textView = new TextView(getContext());
        textView.setLayoutParams(mLinearParams);
        textView.setSingleLine();
        textView.setPadding(mTextPadding, mTextPadding, mTextPadding, mTextPadding);
        textView.setEllipsize(TextUtils.TruncateAt.END);
        textView.setTextColor(mTextColor);
        textView.setVisibility(INVISIBLE);
        textView.setText(text);
        if (mLeftDrawable > 0) {
            Drawable drawable = getContext().getResources().getDrawable(mLeftDrawable);
            // Bitmap bitmap = BitmapFactory.decodeResource(getContext().getResources(), mLeftDrawable);
            // Drawable drawable = new BitmapDrawable(getContext().getResources(), bitmap);
            drawable.setBounds(0, 0, 10, 10);
            textView.setCompoundDrawablePadding(10);
            textView.setCompoundDrawables(drawable, null, null, null);
        }
        textView.setOnClickListener(this);
        // 设置字体大小
        if (mTextSize > 0) {
            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
        }
        return textView;
    }

    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(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(ANIMATION_DURATION);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                pause();
                break;
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                resume();
                break;
        }
        return true;
    }

    public void resume() {
        // 只有一页时不进行切换
        if (mPageCount < 1) return;
        if (mRunnable == null) {
            mRunnable = new RollingRunnable();
        } else {
            mHandler.removeCallbacks(mRunnable);
        }
        mHandler.postDelayed(mRunnable, mDuration);
    }

    public void pause() {
        if (mRunnable != null) {
            mHandler.removeCallbacks(mRunnable);
        }
    }

    @Override
    public void onClick(View v) {
        if (null == mClickListener) return;
        TextView textView = (TextView) v;
        mClickListener.onItemClick(textView);
        textView.setTextColor(mClickColor);
    }

    /**
     * 隐藏当前页,显示下一页任务
     */
    public class RollingRunnable implements Runnable {

        @Override
        public void run() {
            // 隐藏当前页
            LinearLayout currentView = mRollingPages.get(mCurrentPage);
            currentView.setVisibility(INVISIBLE);
            if (mExitAnimSet != null) {
                currentView.startAnimation(mExitAnimSet);// mExitUpAnim);
            }
            mCurrentPage++;
            if (mCurrentPage >= mPageCount) {
                mCurrentPage = 0;
            }
            // 显示下一页
            LinearLayout nextView = mRollingPages.get(mCurrentPage);
            nextView.setVisibility(VISIBLE);
            setVisibility(nextView);
            if (mEnterAnimSet != null) {
                nextView.startAnimation(mEnterAnimSet);// mEnterDownAnim);
            }
            mHandler.postDelayed(this, mDuration);
        }
    }

    public class Rotate3dAnimation extends Animation {
        private final float mFromDegrees;
        private final float mToDegrees;
        private final boolean mTurnIn;
        private final boolean mTurnUp;
        private float mCenterX;
        private float mCenterY;
        private Camera mCamera;

        public Rotate3dAnimation(float fromDegrees, float toDegrees, boolean turnIn, boolean turnUp) {
            mFromDegrees = fromDegrees;
            mToDegrees = toDegrees;
            mTurnIn = turnIn;
            mTurnUp = turnUp;
        }

        @Override
        public void initialize(int width, int height, int parentWidth, int parentHeight) {
            super.initialize(width, height, parentWidth, parentHeight);
            mCamera = new Camera();
            mCenterY = getHeight() / 2;
            mCenterX = getWidth() / 2;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            final float fromDegrees = mFromDegrees;
            float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);

            final float centerX = mCenterX;
            final float centerY = mCenterY;
            final Camera camera = mCamera;
            final int derection = mTurnUp ? 1 : -1;

            final Matrix matrix = t.getMatrix();

            camera.save();
            if (mTurnIn) {
                camera.translate(0.0f, derection * mCenterY * (interpolatedTime - 1.0f), 0.0f);
            } else {
                camera.translate(0.0f, derection * mCenterY * (interpolatedTime), 0.0f);
            }
            camera.rotateX(degrees);
            camera.getMatrix(matrix);
            camera.restore();

            matrix.preTranslate(-centerX, -centerY);
            matrix.postTranslate(centerX, centerY);
        }
    }

    public interface onItemClickListener {
        void onItemClick(TextView v);
    }
}

使用

// 初始化号外列表
List<String> haowaiArray = new ArrayList<>();
haowaiArray.add("[母婴天地] 买尿不湿送婴儿手口湿巾");
haowaiArray.add("[利民商店] 满100免费配送");
haowaiArray.add("[果之家] 泰国金枕榴莲8元/kg");
haowaiArray.add("[户外运动] 户外运动专业装备搜集");
haowaiArray.add("[天天特价] 只要9.9还包邮");
haowaiArray.add("[尖端潮品] 折叠电动车");
haowaiArray.add("[黑科技] 智能VR带你装13");
haowaiArray.add("[旅行必备] 太阳能充电宝-永不断电");
// 绑定数据
mRollingView.setPageSize(4);
mRollingView.setClickColor(0xff888888);
mRollingView.setLeftDrawable(R.drawable.drawable_red_dot);
mRollingView.setRollingText(haowaiArray);
mRollingView.setOnItemClickListener(this);

...

@Override
public void onItemClick(TextView v) {
    // handle item click event
}

@Override
public void onResume() {
    super.onResume();
    mRollingView.resume();
}
@Override
public void onPause() {
    super.onPause();
    mRollingView.pause();
}

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    向上滚动公告栏