RecycleView实现画廊效果

先说项目ui需求图:

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

如下图所展示:

RecycleView实现画廊效果_第1张图片

RecycleView实现画廊效果_第2张图片

需求实现:

难点如下:
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实现画廊效果_第3张图片

RecycleView实现画廊效果_第4张图片

RecycleView实现画廊效果_第5张图片

你可能感兴趣的:(Android控件)