Android项目需求 仿淘宝 京东 商城 的 垂直滚动头条

布局

效果图

1.自定义垂直滚动控件VerticalBannerView


import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.LinearLayout;


/**
 * @author: wuxifan
 * @date on 2019-08-19 16:42
 * @email 1095838464@qq.com
 **/
@SuppressWarnings("unused")
public class VerticalBannerView extends LinearLayout implements BaseBannerAdapter.OnDataChangedListener {

    private float mBannerHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,40,getResources().getDisplayMetrics());
    private int mGap = 4000;
    private int mAnimDuration = 1000;

    private BaseBannerAdapter mAdapter;

    private View mFirstView;
    private View mSecondView;

    private int mPosition;

    private boolean isStarted;
    private Paint mDebugPaint;

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

    public VerticalBannerView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public VerticalBannerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context,attrs,defStyleAttr);
    }


    /**
     * bannerHeight banner的高度
     * animDuration 每次切换动画时间
     * gap banner切换时间
     *
     * */
    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
        setOrientation(VERTICAL);
        mDebugPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.VerticalBannerView);
        mGap = array.getInteger(R.styleable.VerticalBannerView_gap,mGap);
        mAnimDuration = array.getInteger(R.styleable.VerticalBannerView_animDuration,mAnimDuration);

        if(mGap <= mAnimDuration){
            mGap = 4000;
            mAnimDuration = 1000;
        }

        array.recycle();
    }

    /**
     * 设置banner的数据
     * */
    public void setAdapter(BaseBannerAdapter adapter){
        if(adapter == null){
            throw new RuntimeException("adapter must not be null");
        }
        if(mAdapter != null){
            throw new RuntimeException("you have already set an Adapter");
        }
        this.mAdapter = adapter;
        mAdapter.setOnDataChangedListener(this);
        setupAdapter();
    }

    public void start(){
        if(mAdapter == null){
            throw new RuntimeException("you must call setAdapter() before start");
        }

        if(!isStarted && mAdapter.getCount() > 1){
            isStarted = true;
            postDelayed(mRunnable,mGap);
        }
    }

    public void stop(){
        removeCallbacks(mRunnable);
        isStarted = false;
    }


    private void setupAdapter() {
        removeAllViews();

        if(mAdapter.getCount() == 1){
            mFirstView = mAdapter.getView(this);
            mAdapter.setItem(mFirstView,mAdapter.getItem(0));
            addView(mFirstView);
        }else{
            mFirstView = mAdapter.getView(this);
            mSecondView = mAdapter.getView(this);
            mAdapter.setItem(mFirstView,mAdapter.getItem(0));
            mAdapter.setItem(mSecondView,mAdapter.getItem(1));
            addView(mFirstView);
            addView(mSecondView);

            mPosition = 1;
            isStarted = false;
        }
        setBackgroundDrawable(mFirstView.getBackground());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if(LayoutParams.WRAP_CONTENT == getLayoutParams().height){
            getLayoutParams().height = (int) mBannerHeight;
        }else{
            mBannerHeight = getHeight();
        }
        if(isInEditMode()){
            setBackgroundColor(Color.GRAY);
            return;
        }
        if(mFirstView != null){
            mFirstView.getLayoutParams().height = (int) mBannerHeight;
        }
        if(mSecondView != null){
            mSecondView.getLayoutParams().height = (int) mBannerHeight;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(isInEditMode()){
            mDebugPaint.setColor(Color.WHITE);
            mDebugPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,16,getResources().getDisplayMetrics()));
            mDebugPaint.setStyle(Paint.Style.STROKE);
            canvas.drawText("banner is here",20,getHeight()*2/3,mDebugPaint);
        }
    }

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


    private void performSwitch(){
        ObjectAnimator animator1 = ObjectAnimator.ofFloat(mFirstView,"translationY",mFirstView.getTranslationY()-mBannerHeight);
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(mSecondView,"translationY",mSecondView.getTranslationY()-mBannerHeight);
        AnimatorSet set = new AnimatorSet();
        set.playTogether(animator1,animator2);
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mFirstView.setTranslationY(0);
                mSecondView.setTranslationY(0);
                View removedView = getChildAt(0);
                mPosition++;
                mAdapter.setItem(removedView,mAdapter.getItem(mPosition%mAdapter.getCount()));
                removeView(removedView);
                addView(removedView,1);
            }

        });
        set.setDuration(mAnimDuration);
        set.start();
    }
    private AnimRunnable mRunnable = new AnimRunnable();
    private class AnimRunnable implements Runnable{

        @Override
        public void run() {
            performSwitch();
            postDelayed(this,mGap);
        }
    }


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

    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }
    public int getmPosition(){
        return (mPosition-1)%mAdapter.getCount();
    }
}

2.所用到的styeable(在values/atrs.xml里定义一个styleable)

<declare-styleable name="VerticalBannerView">
        <attr name="gap" format="integer" />
        <attr name="animDuration" format="integer"/>
</declare-styleable>

3.自定义BaseBannerAdapter


import android.view.View;

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

/**
 * @author: wuxifan
 * @date on 2019-08-19 16:43
 * @email 1095838464@qq.com
 * 作用:
 **/
@SuppressWarnings("unused")
public abstract class BaseBannerAdapter<T> {
    private List<T> mDatas;
    private OnDataChangedListener mOnDataChangedListener;

    public BaseBannerAdapter(List<T> datas) {
        mDatas = datas;
        if (datas == null || datas.isEmpty()) {
            throw new RuntimeException("nothing to show");
        }
    }

    public BaseBannerAdapter(T[] datas) {
        mDatas = new ArrayList<>(Arrays.asList(datas));
    }

    /**
     * 设置banner填充的数据
     */
    public void setData(List<T> datas) {
        this.mDatas = datas;
        notifyDataChanged();
    }

    void notifyDataChanged() {
        mOnDataChangedListener.onChanged();
    }

    void setOnDataChangedListener(OnDataChangedListener listener) {
        mOnDataChangedListener = listener;
    }

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

    public T getItem(int position) {
        return mDatas.get(position);
    }

    /**
     * 设置banner的样式
     */
    public abstract View getView(VerticalBannerView parent);

    /**
     * 设置banner的数据
     */
    public abstract void setItem(View view, T data);


    interface OnDataChangedListener {
        void onChanged();
    }
}

4.实现自己的VerticaBannerSampleAdapter 


import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import java.util.List;

/**
 * @author: wuxifan
 * @date on 2019-08-19 16:46
 * @email 1095838464@qq.com
 * 作用:
 **/
public class VerticaBannerSampleAdapter extends BaseBannerAdapter<BannerList> {

    private List<BannerList> mDatas;

    public VerticaBannerSampleAdapter(List<BannerList> datas) {
        super(datas);
    }

    @Override
    public View getView(VerticalBannerView parent) {
        return LayoutInflater.from(parent.getContext()).inflate(R.layout.item_vertical_banner_tv, null);
    }

    @Override
    public void setItem(final View view, final BannerList data) {
        TextView tv = (TextView) view.findViewById(R.id.tv_title);
        tv.setText(data.getTitle());
        //你可以增加点击事件
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //比如打开url
        Toast.makeText(view.getContext(),data.getUrl(),Toast.LENGTH_SHORT).show();
            }
        });
    }


}

5.用来垂直展示的布局文件 这里简单的就一个TextView

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
   >
    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:gravity="center_vertical"
        android:layout_height="wrap_content"
        android:layout_marginRight="@dimen/dp_10"
        android:ellipsize="marquee"
        android:singleLine="true"
        android:text="hahahhahahahahhahahahahahhah" />
</LinearLayout>

6.正式布局引用

 <!--切换时长2000ms   动画间隔900ms-->
                            <com.wuxifan.demo.module.main.other.VerticalBannerView
                                android:id="@+id/banner_01"
                                android:layout_width="match_parent"
                                android:layout_height="@dimen/dp_18"
                                android:layout_marginTop="@dimen/dp_5"
                                android:layout_marginBottom="@dimen/dp_10"
                                app:animDuration="900"
                                app:gap="5500" />

7.代码中设置

  @BindView(R.id.banner_01)
    VerticalBannerView banner01;
private void setHeaderArticle(List<BannerList> bannerLists) {
        final VerticaBannerSampleAdapter adapter = new VerticaBannerSampleAdapter(bannerLists);
        banner01.setAdapter(adapter);
        banner01.start();

        btnLookDetail.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
//banner01.getmPostion()为当前显示的滚动item的下标   
 Toast.makeText(view.getContext(),bannerLists.get(banner01.getmPosition()).getUrl(),Toast.LENGTH_SHORT).show();
            }
        });
    }

8.BannerList这个是自定义的javeBean 自行替换成所需的咯


import android.text.TextUtils;

import java.io.Serializable;



public class BannerList implements Serializable {

    private String title;
    private String url;
    private String id;
    private String sub_title;
    private String shortCutName;
    private String iconUrl;
    private String targetUrl;

    public String getShortCutName() {
        return shortCutName == null ? "" : shortCutName;
    }

    public void setShortCutName(String shortCutName) {
        this.shortCutName = shortCutName;
    }

    public String getIconUrl() {
        return iconUrl == null ? "" : iconUrl;
    }

    public void setIconUrl(String iconUrl) {
        this.iconUrl = iconUrl;
    }

    public String getTargetUrl() {
        return targetUrl == null ? "" : targetUrl;
    }

    public void setTargetUrl(String targetUrl) {
        this.targetUrl = targetUrl;
    }

    public String getId() {
        return id == null ? "" : id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getSub_title() {
        return sub_title == null ? "" : sub_title;
    }

    public void setSub_title(String sub_title) {
        this.sub_title = sub_title;
    }

    public String getTitle() {
        return title == null ? "" : title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getUrl() {
        return url == null ? "" : url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}

站在网上各位大神的肩膀上总结的  特此感谢

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值