用RecycleView实现一个双重柱状图,扩展实现无限轮播(已更新)

4,869 阅读17分钟

需求

实习第一个星期得到了一个需求,其中有个模块就下图所示的柱状图,一开始我对这一块的想法是上网找个插件去实现轮播图这个模块,当做完其他模块后,开始柱状图模块的我犯难了。我找了下Android中的柱状图的实现都是以MPAndroidChart等为首的一些开源库。几乎所有的柱状图的实现都是普通的柱状图,然后就是分组形势的柱状图实现,在介绍视频中我看到了类似我需求的柱状图,但我感觉好麻烦。因为感觉项目整体的模块就这一块使用了一个简单的柱状图,感觉使用一些开源库太大材小用了,如果是数据分析与展示的那种页面需要用到各种图表展示数据我可能这种自己实现的想法就会小很多。

看一下这个需求,上一个需求模块我用RecycleView的网格布局实现的,我看到这个模块的时候我觉得啊,它真的也能用RecycleView去实现,我发现其实就是一个横板的RecycleView,难点呢就在那个条上,就是上面是圆角的矩形,我就想到了使用自定义View去实现这个条的部分。

根据需求可以将该模块的柱状图实现,需要使用以下几个技术:

1.RecycleView

2.自定义View

知道了实现技术,就需要实现过程,观察需求的图片可以发现班组一的总人数是最高的,他的背景(橙色)条也是最高的,占满了条形区域的全部位置,就可以知道我们如果需要实现该功能需要一个比值(总数:所有班组中最大的总人数)。再看内部的条(蓝色)的长度,它的规律很好看出,最后能发现是自身头上两个参数的比较(在岗:总数)

开工

自定义柱子

其实我做完后发现难点就在这个自定义柱子上,做到后面我遇到一个坑,是自己对绘图这一块有点欠缺的原因,等说到那再给大家说坑在哪。

自定义一个View其实已经是基础,很多面试都会问到,也有很多博客去详解了,我就不班门弄虎了。

自定义柱子代码

public class BarChartItem extends View {
    private static final String TAG = "BarCharView";
    private Paint paint;
    private int measuredWidth;
    private int measuredHeight;
    private double ratio;
    private double barRatio;
    private GradientDrawable gradientDrawable;

    public BarChartItem(Context context) {
        super(context);
        initPaint();
    }

    public BarChartItem(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

    public BarChartItem(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint();
    }

    //设置第一层bar的比例
    public void setRatio(double ratio) {
        this.ratio = ratio;
        invalidate();
    }

    //设置内部bar的比例
    public void setBarRatio(double barRatio) {
        this.barRatio = barRatio;
        invalidate();
    }

    //初始化画笔与设置顶部圆角
    private void initPaint() {
        paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(getResources().getColor(R.color.white));
        //抗锯齿
        paint.setAntiAlias(true);

        gradientDrawable = new GradientDrawable();

        //设置顶部圆角
        gradientDrawable.setCornerRadii(new float[]{15,15,15,15,0,0,0,0});
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measuredWidth = getMeasuredWidth();
        measuredHeight = getMeasuredHeight();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //第一层bar
        if (ratio != 0){
            canvas.save();
            myDraw(canvas,ratio,getResources().getColor(R.color.colorBarBack));
        }
        //内部bar
        if (barRatio != 0){
            canvas.restore();
            myDraw(canvas,ratio * barRatio,getResources().getColor(R.color.colorBarColor));
        }
    }

    private void myDraw(Canvas canvas,double myRatio,int color) {
        gradientDrawable.mutate();
        gradientDrawable.setColor(color);
        int drawHeight = (int) (measuredHeight * myRatio + 0.5); //四舍五入了下
        //这里把画布移到绘画地方的左上角
        canvas.translate(0,measuredHeight - drawHeight);
        //设置绘制矩形
        gradientDrawable.setBounds(0, 0, measuredWidth, drawHeight);
        gradientDrawable.draw(canvas);
    }

}

放了代码了,其实挺简单的,绘制矩形上面的圆角我使用了GradientDrawable的setCornerRadii方法,虽然GradientDrawable呢主要用来设置背景渐变的,GradientDrawable在Android中是shape标签的动态实现,我就使用他来方便的去实现圆角了,然后我们在使用shape去实现圆角的时候会有边缘锯齿,在初始化画笔的时候设置个抗锯齿就要好很多。刚刚说的都是些小小的绘画细节。

然后就是坑的地方了在onDraw中,因为需要绘制两层柱子才能达到效果,踩坑前的代码是这样的:

if (ratio != 0){
    myDraw(canvas,ratio,getResources().getColor(R.color.colorBarBack));
}
if (barRatio != 0){
    myDraw(canvas,ratio * barRatio,getResources().getColor(R.color.colorBarColor));
}

导致效果如下:

前两组数据差异不大,看不出错误,特别是第一组数据的显示效果完全没错(如果把在岗人数改为120他的显示效果也不会错),第三组的数据为什么会这样呢?导致这样的原因是什么呢?我捋了一遍思路,思路是没错的。根据后面一层柱子的显示效果发现绘制第一层是完全正确的,只好打印了下每一层柱子绘制的高度,可能是计算第二层绘制高度的时候有误,结果打印结果是正确的,第三组数据内层柱子的绘制高度确实是外层的一半。那肯定错误的原因在绘制上面。

错误锁定在了绘制上面就onDraw()中,跳进myDraw(),计算高度没有任何问题,那绘制本应该没有问题啊,那整个方法出错的地方只能是绘制开始的位置了。(也就是下面这个代码)

//这里把画布移到绘画地方的左上角
canvas.translate(0,measuredHeight - drawHeight);

为什么有坑?

为什么要将画布移到绘画地方的左上角呢?

在Android中,canvas就好比一张桌子,桌子左上角有一个Paint(画笔),每次绘画都必须从左上角开始,屏幕或者你的View的就像一张固定位置不动的白纸,当你要开始写字的时候(我们写字打印都习惯从左上角开始从左往右的写字吧),第一步是计算你想在哪开始写字;第二步是移动桌子,使桌子的左上角移动到开始写字的地方;第三步才是根据规则去写字。

初始化的时候呢,他们就重合了

当我们先去画橙色条纹的时候是应该怎样的呢?(我们在代码中没有移动X轴,却画在了中间,其实是因为画布的宽度其实就导航栏那么宽,高度也是固定的为最高的橙色导航条的高度,所以在后面的原理图中可能会有些不严谨,忽略就好)

如果我们不移动画布的绘制结果就会是:

移动后的原理图呢就是下面这样的:

Canvas2.png

效果呢就是我们要的效果了,在Android中坐标是倒的,绘画的起点是在上方,就比如我们平时画一棵树是从底部开始。

但是在计算机中我们画一棵树却是倒着的,如下图所示:

这就是与我们平常不同的思维方式。

其实Android中真实的坐标系是:

坐标系

所以我们要想实现同一起点,一起从下面长出来的效果,需要的是计算距离顶部的距离,画布移动相应的距离再去绘画其所对应的高。其实就是终点相同,但起点不同。

举个🌰,就像有些人一出生就在终点线了,我们话的柱状图正好是这个事实的体现,出生在终点线附近的人虽然只用画一点就能到终点线,但人数却很少,而且他们是父母在自己生前努力了很多,帮他们把画板移动到了终点线附近的。

那这个为什么会导致上图的错误呢?

简单的说就是没有还原最初的状态。

用一个高级词汇来解释,叫做“参考物”不同,高中学过运动是相对的,根据参考物的不同,物体相对速度也是不同的。用上面的为什么将画布移到左上角的答案来分析。首先画布(桌子)在View(白纸)的左上角他们是对其的,我们画橙色导航条的时候需要将画布(桌子)移动到开始画的地方,然后画布的位置可能已经移动过了(桌子现在可能已经不在左上角了)。我们画第二个蓝色导航条的时候移动的距离呢,是根据画布(桌子)在左上角初始的时候算的,如果再移动的话除非第一次画布(桌子)没有动(第一组数据总人数是最多的,所以根据需求其占的比例是1,就导致measuredHeight等于drawHeight),否则就会导致画布(桌子)移动过头。

再举起个🌰来,就是父母帮他们铺好了路,但他们不太甘心,还是以一个共同的起点去奋斗去帮他们的下一代创造未来,但他们是接力跑的,接着父母的脚步继续前行,就导致了这样的现象,只要父母没有努力过的,他们绘画出来的效果就是正常的。

解决方案

那知道了为什么解决办法就会有的。两种方案:

1.选取第一个为参考物,保留第一次的移动距离,根据第二次所要移动的距离计算出真实的移动距离,移动真实的移动距离。(在本项目中只用计算y轴的相对距离还好,如果加上x轴的话又大大增加了计算量)

2.选取一样的参考物,还原最初移动前的状态,你画完后还完,下一个来不用关心你做了什么。

我们当然选第二种啊,使用canvas.save()与canvas.restore(),这就像c++中的push()(c++11中的emplace())与pop()一样,其实就看成一个状态栈,第一个画之前保存一下状态,画完恢复画前的状态。(我上面代码是第二个画之前恢复状态,效果一样的,但可能会有些歧义吧,因为如果是第三个人来画,我们直接注释第二个人,如果第三个人不知道要恢复状态,那这个错误就又回来,大家可以自己改一下,可以将canvas.save()与canvas.restore()放到myDraw的开始与结束的地方,这样就增加了代码的重用性)

我们最后想要的那个🌰是大家每次的起点相同,每次都从一样的起点出发。

代码

最后呢说了那么多,其实呢就是我被前面那个小小的错误坑了,最后就把完整的代码给到大家吧!(总体实现挺简单的,后面还有扩展,因为项目需求是TV板的展示,所以就没有处理onTouch部分,但最后因为可能班组很多,TV板的该模块需要实现自动滚动效果,我第一想法是像轮播图一样,但轮播图的实现使用的一般是ViewPage,因为ViewPage使用的也是适配器模式,与RecycleView我感觉还挺像的,那就将就使用RecycleView去实现轮播的效果了,思想应该都差不多的)

BarChartItem.java

public class BarChartItem extends View {
    private static final String TAG = "BarCharView";
    private Paint paint;
    private int measuredWidth;
    private int measuredHeight;
    private double ratio;
    private double barRatio;
    private GradientDrawable gradientDrawable;

    public BarChartItem(Context context) {
        super(context);
        initPaint();
    }

    public BarChartItem(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

    public BarChartItem(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint();
    }

    //设置第一层bar的比例
    public void setRatio(double ratio) {
        this.ratio = ratio;
        invalidate();
    }

    //设置内部bar的比例
    public void setBarRatio(double barRatio) {
        this.barRatio = barRatio;
        invalidate();
    }

    //初始化画笔与设置顶部圆角
    private void initPaint() {
        paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(getResources().getColor(R.color.white));
        //抗锯齿
        paint.setAntiAlias(true);

        gradientDrawable = new GradientDrawable();

        //设置顶部圆角
        gradientDrawable.setCornerRadii(new float[]{15,15,15,15,0,0,0,0});
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measuredWidth = getMeasuredWidth();
        measuredHeight = getMeasuredHeight();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //背景(可以填充背景,本例子中无影响)
        canvas.drawRect(0, 0, measuredWidth, measuredHeight, paint);
        //第一层bar
        if (ratio != 0){
            myDraw(canvas,ratio,getResources().getColor(R.color.colorBarBack));
        }
        //内部bar
        if (barRatio != 0){
            myDraw(canvas,ratio * barRatio,getResources().getColor(R.color.colorBarColor));
        }
    }

    private void myDraw(Canvas canvas,double myRatio,int color) {
        canvas.save();
        gradientDrawable.mutate();
        gradientDrawable.setColor(color);
        int drawHeight = (int) (measuredHeight * myRatio + 0.5); //四舍五入了下
        //这里把画布移到绘画地方的左上角
        canvas.translate(0,measuredHeight - drawHeight);
        //设置绘制矩形
        gradientDrawable.setBounds(0, 0, measuredWidth, drawHeight);
        gradientDrawable.draw(canvas);
        canvas.restore();
    }

}

布局代码 item_bar_chart.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:gravity="center_horizontal"
    android:layout_marginLeft="10dp"
    android:layout_marginRight="14dp"
    android:padding="6dp"
    android:layout_width="wrap_content"
    android:layout_height="match_parent">

    <LinearLayout
        android:gravity="center|bottom"
        android:layout_weight="1"
        android:layout_width="wrap_content"
        android:layout_height="0dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/gray"
            android:text="@string/on_guard"
            android:textSize="8sp"/>

        <TextView
            android:id="@+id/on_guard_num"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/gray"
            android:text="230"
            android:textSize="8sp"/>

    </LinearLayout>

    <LinearLayout
        android:gravity="center|top"
        android:layout_weight="1"
        android:layout_marginTop="2dp"
        android:layout_marginBottom="2dp"
        android:layout_width="wrap_content"
        android:layout_height="0dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/balck"
            android:text="@string/guard_sum"
            android:textSize="8sp"/>

        <TextView
            android:id="@+id/guard_sum"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/balck"
            android:text="241"
            android:textSize="8sp"/>

    </LinearLayout>

    <RelativeLayout
        android:layout_weight="8"
        android:id="@+id/rl_bar_chart"
        android:layout_marginTop="4dp"
        android:layout_marginBottom="4dp"
        android:layout_width="wrap_content"
        android:layout_height="0dp">

        <com.example.user.nettydemo.view.BarChartItem
            android:layout_centerHorizontal="true"
            android:id="@+id/sum_bar_chart"
            android:layout_gravity="center_horizontal"
            android:layout_width="14dp"
            android:layout_height="match_parent" />

    </RelativeLayout>

    <TextView
        android:layout_weight="2"
        android:id="@+id/bar_chart_class_type"
        android:textColor="@color/balck"
        android:gravity="center"
        android:text="班组1"
        android:textSize="10sp"
        android:layout_width="match_parent"
        android:layout_height="0dp" />

</LinearLayout>

JavaBean对象 ClassInfoBean.java

public class ClassInfoBean {
    public static int MaxGuardSum = 0;
    public static int WorkerSum = 2444;
    private String OnJobClassName;
    private int onGuardNum;
    private int GuardSum;

    public void setOnJobClassName(String onJobClassName) {
        OnJobClassName = onJobClassName;
    }

    public void setOnGuardNum(int onGuardNum) {
        this.onGuardNum = onGuardNum;
    }

    public void setGuardSum(int guardSum) {
        GuardSum = guardSum;
        if(GuardSum > MaxGuardSum){
            MaxGuardSum = GuardSum;
        }
    }

    public String getOnJobClassName() {
        return OnJobClassName;
    }

    public int getOnGuardNum() {
        return onGuardNum;
    }

    public int getGuardSum() {
        return GuardSum;
    }

    public int getSumRatio() {
        return 0 == MaxGuardSum ? 0 : GuardSum * 100 / MaxGuardSum;
    }

    public int getOnGuardNumRatio() {
        return 0 == GuardSum ? 0 : onGuardNum * 100 / GuardSum;
    }
}

适配器代码 BarChartAdapter.java

public class BarChartAdapter extends RecyclerView.Adapter<BarChartAdapter.ViewHolder> {
    private static final String TAG = "BarChartAdapter";

    private List<ClassInfoBean> mClassInfoList = new ArrayList<ClassInfoBean>();

    public BarChartAdapter(List<ClassInfoBean> ClassInfoList){
        mClassInfoList = ClassInfoList;
    }

    static class ViewHolder extends RecyclerView.ViewHolder{
        TextView OnGuardNumTv;
        TextView GuardSumTv;
        BarChartItem barChartItem;
        TextView ClassTypeNameTv;
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            OnGuardNumTv = (TextView) itemView.findViewById(R.id.on_guard_num);
            GuardSumTv = (TextView) itemView.findViewById(R.id.guard_sum);
            barChartItem = (BarChartItem) itemView.findViewById(R.id.sum_bar_chart);
            ClassTypeNameTv = (TextView) itemView.findViewById(R.id.bar_chart_class_type);
        }
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_bar_chart,parent,false);
        ViewHolder viewHolder = new ViewHolder(view);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        if(mClassInfoList == null || 0 == mClassInfoList.size()){
            holder.barChartItem.setRatio(0);
            holder.barChartItem.setBarRatio(0);
        }else{
            ClassInfoBean classInfoBean = mClassInfoList.get(position);
            holder.OnGuardNumTv.setText(""+classInfoBean.getOnGuardNum());
            holder.GuardSumTv.setText(""+classInfoBean.getGuardSum());
            holder.barChartItem.setRatio(classInfoBean.getSumRatio()/100.0);
            holder.barChartItem.setBarRatio(classInfoBean.getOnGuardNumRatio()/100.0);
            holder.ClassTypeNameTv.setText(classInfoBean.getOnJobClassName());
        }
    }

    @Override
    public int getItemCount() {
        return mClassInfoList.size();
    }
}

所用到的颜色代码 colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
    <color name="white">#FFFFFF</color>
    <color name="colorBarBack">#FFFFCC00</color>
    <color name="colorBarColor">#FF41C7DB</color>
</resources>

MainActivity的实现部分代码(有时间我就把实现该部分抽出来,做成个小demo给大家参考)

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private RecyclerView barChartRcv;

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

        // BarChart测试
        barChartRcv = (RecyclerView) findViewById(R.id.bar_chart_recycle_view);
        initBarChartView();
        //初始化recyclerview
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        barChartRcv.setLayoutManager(linearLayoutManager);
        BarChartAdapter barGraphAdapter = new BarChartAdapter(testClassIndoList);
        barChartRcv.setAdapter(barGraphAdapter);
    }

    List<ClassInfoBean> testClassIndoList = new ArrayList<ClassInfoBean>();
    // 测试BarChart
    private void initBarChartView(){
        ClassInfoBean classInfoBean = new ClassInfoBean();
        classInfoBean.setOnJobClassName("班组1");
        classInfoBean.setOnGuardNum(230);
        classInfoBean.setGuardSum(241);
        testClassIndoList.add(classInfoBean);
        ClassInfoBean classInfoBean2 = new ClassInfoBean();
        classInfoBean2.setOnJobClassName("班组2");
        classInfoBean2.setOnGuardNum(180);
        classInfoBean2.setGuardSum(182);
        testClassIndoList.add(classInfoBean2);
        ClassInfoBean classInfoBean3 = new ClassInfoBean();
        classInfoBean3.setOnJobClassName("班组3");
        classInfoBean3.setOnGuardNum(100);
        classInfoBean3.setGuardSum(200);
        testClassIndoList.add(classInfoBean3);
    }

}

这样就简单的实现了一个静态的双重柱状图。

扩展一

上面实现了静态的需求界面,现在我还没有真实的数据去绑定,所以上面我说的是静态柱状图的实现。然后我的项目是TV板的一个App,那如果班组有多个,超出了范围肯定需要自动滚动去实现。

然后我就先扩展了这部分的代码。MainActivity.java

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    private RecyclerView barChartRcv;

    private LinearLayoutManager bChartlinearLayoutManager;
    private BarChartAdapter barChartAdapter;

    static Handler barChartHandler = new Handler();

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        barChartHandler.post(BarChartLooper);
    }

    Runnable BarChartLooper = new Runnable() {
        @Override
        public void run() {
            int index = bChartlinearLayoutManager.findLastVisibleItemPosition();
            index = (index + 1) % barChartAdapter.getItemCount();
            barChartRcv.smoothScrollToPosition(index);
            barChartHandler.postDelayed(this, 2500);
        }
    };


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

        // BarChart测试
        barChartRcv = (RecyclerView) findViewById(R.id.bar_chart_recycle_view);
        initBarChartView();
        //初始化recyclerview
        bChartlinearLayoutManager = new LinearLayoutManager(this){
            @Override
            public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
                LinearSmoothScroller smoothScroller =
                        new LinearSmoothScroller(recyclerView.getContext()) {
                            @Override
                            protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
                                return 249f / displayMetrics.densityDpi;
                            }
                        };

                smoothScroller.setTargetPosition(position);
                startSmoothScroll(smoothScroller);
            }
        };
        bChartlinearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        barChartRcv.setLayoutManager(bChartlinearLayoutManager);
        barChartAdapter = new BarChartAdapter(testClassIndoList);
        barChartRcv.setAdapter(barChartAdapter);
    }

}

这个电脑上没有录制gif的工具,我就描述一下,实现了自动滑动了,但我自己还不满意,不是banner的那种效果,到了最后一个item就返回第一个item了,而且如果item个数较少的话虽然效果上是不动的,但是它还是去绑定了滑动的功能,还要继续修改。

扩展二

发现实习后真的学习、写博客的时间变得有点紧了,我会尽快更新完这篇的,现在的实现我自己都还不满意,有需要的可以自己取一下上面静态部分的实现。(上面代码部分BarChartAdapter.java只是简单让效果出来了,还有很多不足) 这周需求有了变动,整个界面都重新换了,不过整体还是差不多的,我还是接着这个继续发我后序的一些优化。上面实现了无限的轮播。实现的主要代码是

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        barChartHandler.post(BarChartLooper);
    }

    Runnable BarChartLooper = new Runnable() {
        @Override
        public void run() {
            int index = bChartlinearLayoutManager.findLastVisibleItemPosition();
            index = (index + 1) % barChartAdapter.getItemCount();
            barChartRcv.smoothScrollToPosition(index);
            barChartHandler.postDelayed(this, 2500);
        }
    };

怎么说呢?就是View添加到Window的时候我们就注册了这个handle,每隔2.5s我们就滑动到下一个item,这产生了两个问题。

问题一

1.(不是主要的问题,但我有点强迫症,想要优化的问题)就是如果班组的个数少于5个(一般的电视屏幕显示的是5个,需求图给出的也是5个),他还是会每2.5s去处理这个跳转滑动的事情,本不应该去做的,虽然展示出来的效果给人的感觉是正确的,因为smoothScrollToPosition(int)这个方法如果item出现在屏幕中它就不会去滑动屏幕了(也就是不管他在哪,只要有一部分显示在屏幕中,它是不会动的)。

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        if(bChartlinearLayoutManager.findLastVisibleItemPosition() < barChartAdapter.getItemCount()){
            barChartHandler.post(BarChartLooper);
        }
    }

    Runnable BarChartLooper = new Runnable() {
        @Override
        public void run() {
            int index = bChartlinearLayoutManager.findLastVisibleItemPosition();
            index = (index + 1) % barChartAdapter.getItemCount();
            if(0 == index){
                barChartRcv.scrollToPosition(index);
            }else{
                barChartRcv.smoothScrollToPosition(index);
            }
            barChartHandler.postDelayed(this, 3500);
        }
    };

所以这是我对第一个问题的优化,item的可见数目小于了,item的总数的时候才去触发事件,然后当要滑动到第一个视图的时候呢直接跳过去,而不是滑动回来,为什么不实现最后一个接着第一个呢(banner轮播图效果),其实是可以实现的我们将getItemCount()的值设置大一点就好了,但但但是,如果我们的班组数量本身小于屏幕能显示的数量呢,后面item的就会去补满显示,明明三个不用轮播的,他还是会显示5个视图,其中2个是重复的,再去轮播,而且我们触发事件的条件也就没用了。所以说我觉得这样的实现也是一个很好的选择。

然后就是第一个问题后引发的第二个问题,“一般的电视屏幕显示的是5个,需求图给出的也是5个”与“因为smoothScrollToPosition(int)这个方法如果item出现在屏幕中它就不会去滑动屏幕了”。

问题二

2.在TV电视中,小米盒子的分辨率是和其他大多数电视不同的(小米二代是213,一看就不正规,213这是个啥数字,而且还不是个偶数)然后呢,如果我们显示的item是4个半,总的item数是5个,这我们的方案就无解了,他就不会动了啊,直接没有触发事件,就算触发了滑动事件,它也没有滑动的效果啊,只能采取手动滑动,或者模拟手动滑动去滑动显示那半个item了。 所以呢,为了解决这个问题。

(1)我修改了下item的布局,也就没有固定宽度了。然后呢

(2)我固定了RecycleView显示的item个数。

下面这个是改动的BarChartAdapter.java中的onCreateViewHolder创建ViewHolder的宽度,这样就固定了。因为我用的整体的布局是使用的layout_weight去分配自适应的,所以呢就能获取屏幕的宽度,从而在测量RecycleView的宽度前先知道了RecycleView的宽度。然后呢设置每一个item的宽度(CreateView的时候设置ViewHolder的宽度为RecycleView宽度的五分之一就行了),从而达到固定个数想法。

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_bar_chart,parent,false);
        view.getLayoutParams().width = recyclerViewWidth / 5;
        ViewHolder viewHolder = new ViewHolder(view);
        return viewHolder;
    }

总结

完了,后面虽然还有些小的优化,比如说动画啊。剩下的就按你的想法去优化吧,而且我上面的方法也应该不是最好的实现,没有最好的,只有最适合的,根据你的实际需求去想你自己的解决办法,然后在自己慢慢优化,最后应该会得到一个最适合需求的实现或思路,我要去设计我的新的需求的界面去了,也有个柱状图,虽然也大同小异,但绘制更加麻烦了一点。大家一起加油~