Android之轻量级自定义垂直跑马灯(仿京东、淘宝 广告轮播)、自定义画板

前言:

       此贴在于功能,粘贴可用、阅读可懂,代码优化和UI请按照

需求自行定义。


主要功能:

1.支持自定义轮播布局

2.支持自定义轮播效果

3.支持单/多行轮播显示,随意定制显示条目


GIF:

Android之轻量级自定义垂直跑马灯(仿京东、淘宝 广告轮播)、自定义画板


实现思路:

    垂直跑马灯,定时轮播,继承ViewFlipper实现。此贴没有对ViewFlipper进行详细的源码解析,如有需求请自行查阅。


Begin: ...

1、创建XMarqueeView类继承自ViewFlipper

public class XMarqueeView extends ViewFlipper implements XMarqueeViewAdapter.OnDataChangedListener {
    /**
     * 是否设置动画时间间隔
     */
    private boolean isSetAnimDuration = false;

    /**
     * 是否单行显示
     */
    private boolean isSingleLine = true;

    /**
     * 轮播间隔
     */
    private int interval = 3000;

    /**
     * 动画时间
     */
    private int animDuration = 1000;
    private int textSize = 14;
    private int textColor = Color.parseColor("#888888");
    /**
     * 一次性显示多少个
     */
    private int itemCount = 1;

    private XMarqueeViewAdapter mMarqueeViewAdapter;

    public XMarqueeView(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.XMarqueeView, defStyleAttr, 0);
        if (typedArray != null) {
            isSetAnimDuration = typedArray.getBoolean(R.styleable.XMarqueeView_isSetAnimDuration, false);
            isSingleLine = typedArray.getBoolean(R.styleable.XMarqueeView_isSingleLine, true);
            interval = typedArray.getInteger(R.styleable.XMarqueeView_marquee_interval, interval);
            animDuration = typedArray.getInteger(R.styleable.XMarqueeView_marquee_animDuration, animDuration);
            if (typedArray.hasValue(R.styleable.XMarqueeView_marquee_textSize)) {
                textSize = (int) typedArray.getDimension(R.styleable.XMarqueeView_marquee_textSize, textSize);
                textSize = PxDpUtils.px2sp(context, textSize);
            }
            textColor = typedArray.getColor(R.styleable.XMarqueeView_marquee_textColor, textColor);
            itemCount = typedArray.getInt(R.styleable.XMarqueeView_marquee_count, itemCount);
            typedArray.recycle();
        }
        isSingleLine = itemCount == 1;
        Animation animIn = AnimationUtils.loadAnimation(context, R.anim.anim_marquee_in);
        Animation animOut = AnimationUtils.loadAnimation(context, R.anim.anim_marquee_out);
        if (isSetAnimDuration) {
            animIn.setDuration(animDuration);
            animOut.setDuration(animDuration);
        }
        setInAnimation(animIn);
        setOutAnimation(animOut);
        setFlipInterval(interval);
    }


    public void setAdapter(XMarqueeViewAdapter adapter) {
        if (adapter == null) {
            throw new RuntimeException("adapter must not be null");
        }
        if (mMarqueeViewAdapter != null) {
            throw new RuntimeException("you have already set an Adapter");
        }
        this.mMarqueeViewAdapter = adapter;
        mMarqueeViewAdapter.setOnDataChangedListener(this);
        setData();
    }

    private void setData() {
        removeAllViews();
        int currentIndex = 0;
        int loopconunt = mMarqueeViewAdapter.getItemCount() % itemCount == 0 ? mMarqueeViewAdapter.getItemCount() / itemCount : mMarqueeViewAdapter.getItemCount() / itemCount + 1;
        for (int i = 0; i < loopconunt; i++) {
            if (isSingleLine) {
                View view = mMarqueeViewAdapter.onCreateView(this);
                mMarqueeViewAdapter.onBindView(view, view, currentIndex);
                currentIndex = currentIndex + 1;
                addView(view);
            } else {
                LinearLayout parentView = new LinearLayout(getContext());
                parentView.setOrientation(LinearLayout.VERTICAL);
                parentView.setGravity(Gravity.CENTER);
                parentView.removeAllViews();
                for (int j = 0; j < itemCount; j++) {
                    View view = mMarqueeViewAdapter.onCreateView(this);
                    parentView.addView(view);
                    mMarqueeViewAdapter.onBindView(parentView, view, getRealPosition(j, currentIndex));
                    currentIndex = getRealPosition(j, currentIndex);
                }
                addView(parentView);
            }
        }
        startFlipping();
    }

    private int getRealPosition(int index, int currentIndex) {
        if ((index == 0 && currentIndex == 0) ||
                (currentIndex == mMarqueeViewAdapter.getItemCount() - 1
                        && currentIndex % itemCount == 0)) {
            return 0;
        } else {
            return currentIndex + 1;
        }
    }

    @Override
    public void onChanged() {
        setData();
    }

    @Override
    protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        if (VISIBLE == visibility) {
            startFlipping();
        } else if (GONE == visibility || INVISIBLE == visibility) {
            stopFlipping();
        }
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        startFlipping();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        stopFlipping();
    }
}

2、在res文件夹下创建anim 文件夹,添加动画:

anim_marquee_in.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="300"
        android:fromYDelta="100%p"
        android:toYDelta="0"/>
    <alpha
        android:duration="500"
        android:fromAlpha="0.0"
        android:toAlpha="1.0"/>
</set>

anim_marquee_out.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="400"
        android:fromYDelta="0"
        android:toYDelta="-100%p"/>
    <alpha
        android:duration="500"
        android:fromAlpha="1.0"
        android:toAlpha="0.0"/>
</set>


3、创建XMarqueeViewAdapter抽象类

public abstract class XMarqueeViewAdapter<T> {

    protected List<T> mDatas;
    private OnDataChangedListener mOnDataChangedListener;

    public XMarqueeViewAdapter(List<T> datas) {
        this.mDatas = datas;
        if (datas == null || datas.isEmpty()) {
            throw new RuntimeException("Nothing to Show With XMarqueeView");
        }
    }

    public void setData(List<T> datas) {
        this.mDatas = datas;
        notifyDataChanged();
    }

    public int getItemCount() {
        return mDatas == null ? 0 : mDatas.size();
    }

    public abstract View onCreateView(XMarqueeView parent);

    public abstract void onBindView(View parent, View view, int position);

    public void setOnDataChangedListener(OnDataChangedListener onDataChangedListener) {
        mOnDataChangedListener = onDataChangedListener;
    }

    public void notifyDataChanged() {
        if (mOnDataChangedListener != null) {
            mOnDataChangedListener.onChanged();
        }
    }

    public interface OnDataChangedListener {
        void onChanged();
    }
}

4、需要用到一个Utils和attrs文件

/**
 * 常用工具类
 */
public class PxDpUtils {

    // 将px值转换为dip或dp值
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    // 将dip或dp值转换为px值
    public static int dip2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    // 将px值转换为sp值
    public static int px2sp(Context context, float pxValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (pxValue / fontScale + 0.5f);
    }

    // 将sp值转换为px值
    public static int sp2px(Context context, float spValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }

    // 屏幕宽度(像素)
    public static int getWindowWidth(Activity context) {
        DisplayMetrics metric = new DisplayMetrics();
        context.getWindowManager().getDefaultDisplay().getMetrics(metric);
        return metric.widthPixels;
    }

    // 屏幕高度(像素)
    public static int getWindowHeight(Activity context) {
        DisplayMetrics metric = new DisplayMetrics();
        context.getWindowManager().getDefaultDisplay().getMetrics(metric);
        return metric.heightPixels;
    }

}
<resources>
    <declare-styleable name="XMarqueeView">
        <attr name="isSetAnimDuration" format="boolean"/>
        <attr name="isSingleLine" format="boolean"/>
        <attr name="marquee_count" format="integer"/>
        <attr name="marquee_interval" format="integer|reference"/>
        <attr name="marquee_animDuration" format="integer|reference"/>
        <attr name="marquee_textSize" format="dimension|reference"/>
        <attr name="marquee_textColor" format="color|reference"/>
    </declare-styleable>
</resources>

xml布局就不发上来了,直接调用刚才自定义的XMarqueeView就可以

6、最后上MainActivity的代码:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    xMarqueeView = findViewById(R.id.xMarqueeView);
    btn_change = findViewById(R.id.btn_changeData);

    EvalData = new ArrayList<>();
    EvalLogoList = new ArrayList<>();

    EvalData.add("old data1");
    EvalData.add("old data2");
    EvalLogoList.add("http://pic.616pic.com/ys_b_img/00/66/73/9KnqqgZBFe.jpg");
    EvalLogoList.add("http://pic.616pic.com/ys_b_img/00/69/98/PbzuWBVtkr.jpg");
    adapter = new MarqueeViewAdapter(EvalLogoList, EvalData, MainActivity.this);
    xMarqueeView.setAdapter(adapter);

    btn_change.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            EvalData.clear();
            EvalLogoList.clear();
            EvalData.add("修改后的数据1111");
            EvalData.add("修改后的数据2222");
            EvalLogoList.add("http://pic.616pic.com/ys_b_img/00/68/81/Z69qGomd7n.jpg");
            EvalLogoList.add("http://pic.616pic.com/ys_b_img/00/70/98/s0qjlzyiPU.jpg");
            adapter.notifyDataChanged();
        }
    });


}
整个流程就是这样,如有疑问请留言。END...