欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

RecycleView实现画廊效果

程序员文章站 2022-03-09 22:41:09
...

先说项目ui需求图:

  1. 滑动到第一项的时候,不可再向左滑动,并且左侧显示:“左侧没有内容”。
  2. 滑动到最后一项的时候,不可再向右滑动,并且右侧显示:“右侧没有内容”。
  3. 用户在开始滑动的时候,“左侧没有内容”或是“右侧没有内容”的view要立即消失。
  4. 当前item需要有放大效果,并且有边框。
  5. 不是当前的item需要有缩小效果,无边框。

如下图所展示:

RecycleView实现画廊效果

RecycleView实现画廊效果

需求实现:

难点如下:
1. 怎么用RecycelView实现这种画廊效果。
2. 选中item的边框怎么处理。
3. 最左侧和最右侧的无内容view怎么展示和隐藏的问题。

先说第一个难点怎么解决:

我曾经试图尝试过用viewpager做,但是效果不理想,然后在github上查找到了一款非常符合我们项目需求的控件:
DiscreteScrollView,这个控件基本上满足了我们的需求,只需要细节调整即可。文档中有描述怎么使用,参照导入就好。

第二个难点:选中item的边框处理:

在Activity中实现 DiscreteScrollView.OnItemChangedListener,在这个方法中,可以拿到当前、前一个、后一个RecyclerView.ViewHolder来做背景边框特殊处理。
当然,我这种方法可能不是最好的,因为activit中展示的时候,只能看到3个item,如果需求设计要看到5个item,处理起来就更麻烦一点了。

@Override
    public void onCurrentItemChanged(@Nullable RecyclerView.ViewHolder viewHolder, int adapterPosition) {
        Timber.d("onCurrentItemChanged %d", adapterPosition);
        StepAdapter.ViewHolder holder = (StepAdapter.ViewHolder) viewHolder;//当前选中的
        holder.rlLayout.setBackgroundResource(R.drawable.bg_step_shape);
        if (adapterPosition == 0) {
            StepAdapter.ViewHolder viewHolder0 = (StepAdapter.ViewHolder) mDsvStep.getViewHolder(adapterPosition + 1);
            viewHolder0.rlLayout.setBackgroundResource(0);
        } else if (adapterPosition > 0 && adapterPosition < mStepBeanList.size() - 1) {//还没有滑动到最后:
            StepAdapter.ViewHolder viewHolder1 = (StepAdapter.ViewHolder) mDsvStep.getViewHolder(adapterPosition - 1);
            viewHolder1.rlLayout.setBackgroundResource(0);
            StepAdapter.ViewHolder viewHolder2 = (StepAdapter.ViewHolder) mDsvStep.getViewHolder(adapterPosition + 1);
            viewHolder2.rlLayout.setBackgroundResource(0);
        } else {
            StepAdapter.ViewHolder viewHolder3 = (StepAdapter.ViewHolder) mDsvStep.getViewHolder(adapterPosition - 1);
            viewHolder3.rlLayout.setBackgroundResource(0);
        }

        onItemChanged(adapterPosition);
        showLeftRightView(adapterPosition);

    }

这个边框的问题就基本处理完毕了,但是还有一个特殊的地方:我们的app是一款可以语音控制的产品,比如说当前在第一步,用户可以说到第十步。那就是从第一步直接跳转到第十步,调用DiscreteScrollView的scrollToPosition(position)方法可以跳转到第十步,但是第十步却没有边框,因为这个方法不会回调onCurrentItemChanged,所以需要我们对这种情况也要做下边框特殊处理。
我开始的方法是通过position,用DiscreteScrollView拿到当前的viewholder, 仿照onCurrentItemChanged中的方法来做,但是结果是不行的,因为通过这个拿到的viewholder有时候是空的。这里涉及到了viewholder的缓存机制问题:用户在第一步,能看到的只有第二项,第十项根本在屏幕之外,所以这个时候去拿第十项的viewholder肯定是空的,那么如何处理呢?
有个小技巧:如果去拿viewholer是空的,那么说明这个viewholder还不在缓存之列,但是肯定会走Adapter中的onBindViewHolder方法,因此,可以在这里配合adapter做处理,总结就是如下:

//这个是在activity中:
 /**
     * scrollveiw 滑动到对应的页面并播报处理
     *
     * @param position:角标从0开始算起,
     */
    private void onItemVoiceChanged(int position) {

        showLeftRightView(position);
        Timber.d("onItemVoiceChanged %d", position);
        mTvStep.setText((position + 1) + " / " + mStepBeanList.size());
        String text = mStepBeanList.get(position).getText();
        mTvContent.setText(text);
        mDsvStep.scrollToPosition(position);
        TtsVoiceManager.getInstance().ttsVoice(text);

        //设置背景颜色
        RecyclerView.ViewHolder viewHolder = mDsvStep.getViewHolder(position);
        StepAdapter.ViewHolder holder = (StepAdapter.ViewHolder) viewHolder;//当前选中的
        if (holder != null) {
            holder.rlLayout.setBackgroundResource(R.drawable.bg_step_shape);
        }else{
            Timber.d("holder 为空");
            //通知:adapterbindholer 设置背景为方框
            mStepAdapter.setChangeBackround(true);
        }

        if (position == 0) {
            StepAdapter.ViewHolder viewHolder0 = (StepAdapter.ViewHolder) mDsvStep.getViewHolder(position + 1);
            if (viewHolder0 != null) {
                viewHolder0.rlLayout.setBackgroundResource(0);
            }else{
                mStepAdapter.setChangeBackround(false);
            }

        } else if (position > 0 && position < mStepBeanList.size() - 1) {//还没有滑动到最后:
            StepAdapter.ViewHolder viewHolder1 = (StepAdapter.ViewHolder) mDsvStep.getViewHolder(position - 1);
            if (viewHolder1 != null) {
                viewHolder1.rlLayout.setBackgroundResource(0);
            }else{
                mStepAdapter.setChangeBackround(false);
            }

            StepAdapter.ViewHolder viewHolder2 = (StepAdapter.ViewHolder) mDsvStep.getViewHolder(position + 1);
            if (viewHolder2 != null) {
                viewHolder2.rlLayout.setBackgroundResource(0);
            }else{
                mStepAdapter.setChangeBackround(false);
            }

        } else {
            StepAdapter.ViewHolder viewHolder3 = (StepAdapter.ViewHolder) mDsvStep.getViewHolder(position - 1);

            if (viewHolder3 != null) {
                viewHolder3.rlLayout.setBackgroundResource(0);
            }else{
                mStepAdapter.setChangeBackround(false);
            }
        }


    }

//这个是在adapter中
 @Override
    public void onBindViewHolder(@NonNull StepAdapter.ViewHolder holder, int position) {
        Timber.d("position%d",position);
        RecipeBean.StepsBean stepsBean = data.get(position);
        ImageLoaderUtil.getInstance().loadImage(holder.ivImage,R.drawable.list_no_image_1,stepsBean.getPic());
        if(isShowBackground){
            holder.rlLayout.setBackgroundResource(R.drawable.bg_step_shape);
        }else{
            holder.rlLayout.setBackgroundResource(0);
        }

        holder.tvIndex.setText((position+1)+"");
    }

通过以上方法,就可以做到手势滑动和语音滑动一模一样的效果了。

最后一个难题:最左侧和最右侧的无内容view怎么展示和隐藏

开始思路是将无内容的view添加到adapter中处理,结果不行,因为要处理DiscreteScrollView的滑动问题,比较复杂了。最后想出在activity布局中增加这两个布局,和DiscreteScrollView重叠在一起,如下:

<com.yarolegovich.discretescrollview.DiscreteScrollView
        android:id="@+id/dsv_step"
        android:layout_width="match_parent"
        android:layout_height="560dp"
        android:layout_below="@id/tv_step"
        app:dsv_orientation="horizontal"
        android:layout_marginTop="10dp"
        />
.........
.........
.........
 <RelativeLayout
        android:id="@+id/rl_left"
        android:layout_width="400dp"
        android:layout_height="560dp"
        android:layout_below="@id/tv_step"
        android:layout_marginTop="10dp"
        android:layout_marginRight="148dp"
        >

        <ImageView
            android:id="@+id/iv_left"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/steps_cor"
            android:layout_centerVertical="true"
            android:layout_alignParentRight="true"
            />

        <TextView
            android:id="@+id/tv_left"
            android:layout_width="25dp"
            android:layout_height="match_parent"
            android:text="左侧没有内容啦"
            android:gravity="center"
            android:textSize="24sp"
            android:textColor="#B7B7B7"
            android:layout_centerVertical="true"
            android:layout_toLeftOf="@id/iv_left"
            android:layout_marginRight="90dp"
            />

    </RelativeLayout>

剩下的就是监听DiscreteScrollView的滑动和空白view的展示和隐藏问题。幸运的是DiscreteScrollView提供了多个监听滑动的方法:在acitivity中实现DiscreteScrollView.ScrollStateChangeListener

 @Override
    public void onScrollStart(@NonNull RecyclerView.ViewHolder currentItemHolder, int adapterPosition) {
        Timber.d("onScrollStart %d", adapterPosition);
        if (adapterPosition == 0) {//如果是0的位置就开始滑动:那么左侧的图片不要显示
            mRlLeft.setVisibility(View.GONE);
        } else if (adapterPosition == mStepBeanList.size() - 1) {//从最后一个开始滑动
            mRlRight.setVisibility(View.GONE);
        }

    }

    @Override
    public void onScrollEnd(@NonNull RecyclerView.ViewHolder currentItemHolder, int adapterPosition) {
        Timber.d("onScrollEnd %d", adapterPosition);

    }

    @Override
    public void onScroll(float scrollPosition, int currentPosition, int newPosition, @Nullable RecyclerView.ViewHolder currentHolder, @Nullable RecyclerView.ViewHolder newCurrent) {
        Log.d("onScroll", "scrollPosition:" + scrollPosition + "currentPosition:" + currentPosition +
                "newPosition:" + newPosition);
    }

另外,需要注意的是:onScrollEnd之后,会回调onCurrentItemChanged的方法,所以这里 还需要根据adapterPosition最终停留的位置做左侧和右侧空白view的展示处理,以便能有更好的体验效果:

 @Override
    public void onCurrentItemChanged(@Nullable RecyclerView.ViewHolder viewHolder, int adapterPosition) {

............
............
............
 showLeftRightView(adapterPosition);//控制左侧/右侧空白view的展示:

}


/**
     * 左右空布局的展示
     * adapterPosition
     */
    public void showLeftRightView(int adapterPosition) {
        if (adapterPosition == 0) {
            mRlLeft.setVisibility(View.VISIBLE);
            mRlRight.setVisibility(View.GONE);
        } else if (adapterPosition == mStepBeanList.size() - 1) {
            mRlLeft.setVisibility(View.GONE);
            mRlRight.setVisibility(View.VISIBLE);
        } else {
            mRlLeft.setVisibility(View.GONE);
            mRlRight.setVisibility(View.GONE);
        }
    }

通过DiscreteScrollView,配合一些监听和技巧以及细节特殊化处理,就能完美实现ui效果图了。

真实效果图如下:
RecycleView实现画廊效果

RecycleView实现画廊效果

RecycleView实现画廊效果