【问题标题】:RecyclerView with custom shaped items带有自定义形状项目的 RecyclerView
【发布时间】:2017-07-11 07:08:31
【问题描述】:

我创建了一个自定义形状的图像视图。如果您在滚动视图中使用它,它工作正常。但是当我尝试在回收站视图中使用它时,我观察到了一种奇怪的行为。除非您向下滚动(参见第二张图片),否则图像不会被绘制并显示间隙(参见第一张图片)。向上滚动时也会发生同样的情况。

我想知道如何避免这些差距。你能指出我在哪里做错了吗?感谢您的帮助。

初始状态或向上滚动后:

向下滚动后:

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.support.v7.widget.AppCompatImageView;
import android.util.AttributeSet;

/**
 * Created by santalu on 7/4/17.
 */

public class DiagonalImageView extends AppCompatImageView {

    public static final int TOP = 0;
    public static final int MIDDLE = 1;
    public static final int BOTTOM = 2;

    private final Path mClipPath = new Path();
    private final Path mLinePath = new Path();

    private final Paint mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    private int mPosition;
    private int mOverlap;
    private int mLineColor;
    private int mLineSize;

    private boolean mMaskEnabled = true;

    public DiagonalImageView(Context context) {
        super(context);
        init(context, null);
    }

    public DiagonalImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        if (attrs == null) {
            return;
        }

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ShowCaseImageView);
        try {
            mPosition = a.getInt(R.styleable.DiagonalImageView_di_position, TOP);
            mOverlap = a.getDimensionPixelSize(R.styleable.DiagonalImageView_di_overlap, 0);
            mLineSize = a.getDimensionPixelSize(R.styleable.DiagonalImageView_di_lineSize, 0);
            mLineColor = a.getColor(R.styleable.DiagonalImageView_di_lineColor, Color.BLACK);

            mLinePaint.setColor(mLineColor);
            mLinePaint.setStyle(Style.STROKE);
            mLinePaint.setStrokeWidth(mLineSize);
        } finally {
            a.recycle();
        }
    }

    public void setPosition(int position, boolean maskEnabled) {
        mMaskEnabled = maskEnabled;
        setPosition(position);
    }

    public void setPosition(int position) {
        if (mPosition != position) {
            mClipPath.reset();
            mLinePath.reset();
        }
        mPosition = position;
    }

    @Override protected void onDraw(Canvas canvas) {
        int saveCount = canvas.getSaveCount();
        canvas.clipPath(mClipPath);
        super.onDraw(canvas);
        canvas.drawPath(mLinePath, mLinePaint);
        canvas.restoreToCount(saveCount);
    }

    @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (!changed) {
            return;
        }

        if (mMaskEnabled && mClipPath.isEmpty()) {
            int width = getMeasuredWidth();
            int height = getMeasuredHeight();

            if (width <= 0 || height <= 0) {
                return;
            }

            switch (mPosition) {
                case TOP:
                    mClipPath.moveTo(0, 0);
                    mClipPath.lineTo(width, 0);
                    mClipPath.lineTo(width, height - mOverlap);
                    mClipPath.lineTo(0, height);

                    mLinePath.moveTo(0, height);
                    mLinePath.lineTo(width, height - mOverlap);
                    break;
                case MIDDLE:
                    mClipPath.moveTo(0, mOverlap);
                    mClipPath.lineTo(width, 0);
                    mClipPath.lineTo(width, height - mOverlap);
                    mClipPath.lineTo(0, height);

                    mLinePath.moveTo(0, height);
                    mLinePath.lineTo(width, height - mOverlap);
                    break;
                case BOTTOM:
                    mClipPath.moveTo(0, mOverlap);
                    mClipPath.lineTo(width, 0);
                    mClipPath.lineTo(width, height);
                    mClipPath.lineTo(0, height);
                    break;
            }
            mClipPath.close();
            mLinePath.close();
        }
    }
}

如果您有兴趣,我会在此处包含示例应用程序来演示问题

import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.santalu.showcaseimageview.ShowCaseImageView;

public class MainActivity extends AppCompatActivity {

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        int overlap = getResources().getDimensionPixelSize(R.dimen.overlap_size);
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setHasFixedSize(true);
        recyclerView.addItemDecoration(new OverlapItemDecoration(-overlap));
        recyclerView.setAdapter(new SampleAdapter(this));
    }

    static class SampleAdapter extends RecyclerView.Adapter<SampleAdapter.ViewHolder> {
        private final Context mContext;

        SampleAdapter(Context context) {
            mContext = context;
        }

        @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item, parent, false));
        }

        @Override public void onBindViewHolder(ViewHolder holder, int position) {
            holder.bind(position);
        }

        @Override public int getItemCount() {
            return 7;
        }

        class ViewHolder extends RecyclerView.ViewHolder {
            DiagonalImageView image;
            //int overlap;

            ViewHolder(View itemView) {
                super(itemView);
                image = (DiagonalImageView) itemView.findViewById(R.id.image);
                //overlap = -mContext.getResources().getDimensionPixelSize(R.dimen.overlap_size);
            }

            void bind(int position) {
                boolean maskEnabled = getItemCount() > 1;
                //MarginLayoutParams params = (MarginLayoutParams) image.getLayoutParams();
                if (position == 0) {
                    image.setPosition(ShowCaseImageView.TOP, maskEnabled);
                    //params.setMargins(0, 0, 0, 0);
                } else if (position == getItemCount() - 1) {
                    image.setPosition(ShowCaseImageView.BOTTOM, maskEnabled);
                    //params.setMargins(0, overlap, 0, 0);
                } else {
                    image.setPosition(ShowCaseImageView.MIDDLE, maskEnabled);
                    //params.setMargins(0, overlap, 0, 0);
                }
                //image.setLayoutParams(params);
            }
        }
    }

    static class OverlapItemDecoration extends RecyclerView.ItemDecoration {
        private int mOverlap;

        OverlapItemDecoration(int overlap) {
            mOverlap = overlap;
        }

        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            if (parent.getChildAdapterPosition(view) != 0) {
                outRect.top = mOverlap;
            }
        }
    }
}

item.xml

<?xml version="1.0" encoding="utf-8"?>
<com.santalu.diagonalimageview.DiagonalImageView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/image"
    android:layout_width="wrap_content"
    android:layout_height="@dimen/image_height"
    android:scaleType="centerCrop"
    android:src="@drawable/demo"
    app:csi_lineColor="@color/deep_orange"
    app:csi_lineSize="@dimen/line_size"
    app:csi_overlap="@dimen/overlap_size"/>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

【问题讨论】:

    标签: android canvas android-recyclerview imageview shape


    【解决方案1】:

    经过一些研究和尝试,我发现 Path 值不正确,并且特别针对边框设计得很好。 有些情况下它们相互重叠,我认为这会导致图像绘制不正确。

    我重新设计了视图并进行了一些改进。对于未来的读者,这里是最终代码:

    /**
     * Created by santalu on 7/4/17.
     *
     * Note: if position set NONE mask won't be applied
     *
     * POSITION    DIRECTION
     *
     * TOP         LEFT |  RIGHT
     * BOTTOM      LEFT |  RIGHT
     * LEFT        TOP  |  BOTTOM
     * RIGHT       TOP  |  BOTTOM
     */
    
    public class DiagonalImageView extends AppCompatImageView {
    
        private static final String TAG = DiagonalImageView.class.getSimpleName();
    
        public static final int NONE = 0;
        public static final int TOP = 1;
        public static final int RIGHT = 2;
        public static final int BOTTOM = 4;
        public static final int LEFT = 8;
    
        private final Path mClipPath = new Path();
        private final Path mBorderPath = new Path();
    
        private final Paint mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    
        private int mPosition;
        private int mDirection;
        private int mOverlap;
        private int mBorderColor;
        private int mBorderSize;
    
        private boolean mBorderEnabled;
    
        public DiagonalImageView(Context context) {
            super(context);
            init(context, null);
        }
    
        public DiagonalImageView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context, attrs);
        }
    
        private void init(Context context, AttributeSet attrs) {
            if (attrs == null) {
                return;
            }
    
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DiagonalImageView);
            try {
                mPosition = a.getInteger(R.styleable.DiagonalImageView_di_position, NONE);
                mDirection = a.getInteger(R.styleable.DiagonalImageView_di_direction, RIGHT);
                mOverlap = a.getDimensionPixelSize(R.styleable.DiagonalImageView_di_overlap, 0);
                mBorderSize = a.getDimensionPixelSize(R.styleable.DiagonalImageView_di_borderSize, 0);
                mBorderColor = a.getColor(R.styleable.DiagonalImageView_di_borderColor, Color.BLACK);
                mBorderEnabled = a.getBoolean(R.styleable.DiagonalImageView_di_borderEnabled, false);
    
                mBorderPaint.setColor(mBorderColor);
                mBorderPaint.setStyle(Style.STROKE);
                mBorderPaint.setStrokeWidth(mBorderSize);
            } finally {
                a.recycle();
            }
        }
    
        public void set(int position, int direction) {
            if (mPosition != position || mDirection != direction) {
                mClipPath.reset();
                mBorderPath.reset();
            }
            mPosition = position;
            mDirection = direction;
            postInvalidate();
        }
    
        public void setPosition(int position) {
            if (mPosition != position) {
                mClipPath.reset();
                mBorderPath.reset();
            }
            mPosition = position;
            postInvalidate();
        }
    
        public void setDirection(int direction) {
            if (mDirection != direction) {
                mClipPath.reset();
                mBorderPath.reset();
            }
            mDirection = direction;
            postInvalidate();
        }
    
        public void setBorderEnabled(boolean enabled) {
            mBorderEnabled = enabled;
            postInvalidate();
        }
    
        @Override protected void onDraw(Canvas canvas) {
            if (mClipPath.isEmpty()) {
                super.onDraw(canvas);
                return;
            }
    
            int saveCount = canvas.save();
            canvas.clipPath(mClipPath);
            super.onDraw(canvas);
            if (!mBorderPath.isEmpty()) {
                canvas.drawPath(mBorderPath, mBorderPaint);
            }
            canvas.restoreToCount(saveCount);
        }
    
        @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);
            if (!changed) {
                return;
            }
    
            if (mClipPath.isEmpty()) {
                int width = getMeasuredWidth();
                int height = getMeasuredHeight();
    
                if (width <= 0 || height <= 0) {
                    return;
                }
    
                mClipPath.reset();
                mBorderPath.reset();
    
                switch (mPosition) {
                    case TOP:
                        if (mDirection == LEFT) {
                            mClipPath.moveTo(0, 0);
                            mClipPath.lineTo(width, mOverlap);
                            mClipPath.lineTo(width, height);
                            mClipPath.lineTo(0, height);
    
                            if (mBorderEnabled) {
                                mBorderPath.moveTo(0, 0);
                                mBorderPath.lineTo(width, mOverlap);
                            }
                        } else {
                            mClipPath.moveTo(0, mOverlap);
                            mClipPath.lineTo(width, 0);
                            mClipPath.lineTo(width, height);
                            mClipPath.lineTo(0, height);
    
                            if (mBorderEnabled) {
                                mBorderPath.moveTo(0, mOverlap);
                                mBorderPath.lineTo(width, 0);
                            }
                        }
                        break;
                    case RIGHT:
                        if (mDirection == TOP) {
                            mClipPath.moveTo(0, 0);
                            mClipPath.lineTo(width, 0);
                            mClipPath.lineTo(width - mOverlap, height);
                            mClipPath.lineTo(0, height);
    
                            if (mBorderEnabled) {
                                mBorderPath.moveTo(width, 0);
                                mBorderPath.lineTo(width - mOverlap, height);
                            }
                        } else {
                            mClipPath.moveTo(0, 0);
                            mClipPath.lineTo(width - mOverlap, 0);
                            mClipPath.lineTo(width, height);
                            mClipPath.lineTo(0, height);
    
                            if (mBorderEnabled) {
                                mBorderPath.moveTo(width - mOverlap, 0);
                                mBorderPath.lineTo(width, height);
                            }
                        }
                        break;
                    case BOTTOM:
                        if (mDirection == LEFT) {
                            mClipPath.moveTo(0, 0);
                            mClipPath.lineTo(width, 0);
                            mClipPath.lineTo(width, height - mOverlap);
                            mClipPath.lineTo(0, height);
    
                            if (mBorderEnabled) {
                                mBorderPath.moveTo(0, height);
                                mBorderPath.lineTo(width, height - mOverlap);
                            }
                        } else {
                            mClipPath.moveTo(0, 0);
                            mClipPath.lineTo(width, 0);
                            mClipPath.lineTo(width, height);
                            mClipPath.lineTo(0, height - mOverlap);
    
                            if (mBorderEnabled) {
                                mBorderPath.moveTo(0, height - mOverlap);
                                mBorderPath.lineTo(width, height);
                            }
                        }
                        break;
                    case LEFT:
                        if (mDirection == TOP) {
                            mClipPath.moveTo(0, 0);
                            mClipPath.lineTo(width, 0);
                            mClipPath.lineTo(width, height);
                            mClipPath.lineTo(mOverlap, height);
    
                            if (mBorderEnabled) {
                                mBorderPath.moveTo(0, 0);
                                mBorderPath.lineTo(mOverlap, height);
                            }
                        } else {
                            mClipPath.moveTo(mOverlap, 0);
                            mClipPath.lineTo(width, 0);
                            mClipPath.lineTo(width, height);
                            mClipPath.lineTo(0, height);
    
                            if (mBorderEnabled) {
                                mBorderPath.moveTo(mOverlap, 0);
                                mBorderPath.lineTo(0, height);
                            }
                        }
                        break;
                }
    
                mClipPath.close();
                mBorderPath.close();
            }
        }
    }
    

    更新: 我将其作为库发布在 Github 上,以备不时之需 Diagonal ImageView

    【讨论】:

      【解决方案2】:

      你应该对行项目 XMl 使用剪切布局这里是链接https://github.com/florent37/DiagonalLayout

      <com.github.florent37.diagonallayout.DiagonalLayout
          android:layout_width="match_parent"
          android:layout_height="250dp"
          android:elevation="10dp"
          app:diagonal_angle="20"
          app:diagonal_position="top"
          app:diagonal_direction="right">
      
          <ImageView
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:scaleType="centerCrop"
              android:src="@drawable/mountains" />
      

      【讨论】:

      • 感谢您的评论。是的,我知道这个图书馆。但正如您从屏幕截图中看到的那样,我提供的 recyclerview 项目相互重叠,并且所有项目都有自己的分隔线(橙色线)。并且图像视图的剪辑路径和分隔路径由重叠高度而不是角度计算,以确保每个项目都尊重它们的边界并正确绘制。我只想知道我的视图或 recyclerview 实现有什么问题。不过谢谢你的建议
      • 除了这个库也会出现同样的问题。尝试在 recyclerview 中使用它。
      • 你是否设置了对角线位置,在 XML 中应该是这样的对角线:diagonal_position="bottom"。我不确定,但如果你检查最后一个索引,你可以使用不同的布局,其中对角线位置设置为底部。
      • 您可以使用不同的布局,也可以使用相同的布局,但您可以使用布局的可见性并在最后一个索引处使用它。
      • 您拥有的库比您可以在适配器类的最后一个索引处设置对角线属性。
      猜你喜欢
      • 2014-06-21
      • 2013-04-17
      • 2014-10-23
      • 2023-01-31
      • 2019-04-27
      • 2018-11-03
      • 1970-01-01
      • 2018-12-17
      • 2010-10-31
      相关资源
      最近更新 更多