具有自定义形状项目的 RecyclerView

RecyclerView with custom shaped items

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

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

初始状态或向上滚动后:

向下滚动后:

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"/>

您应该为行项目使用剪切布局 XMl 这里是 link 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" />

经过一些研究和尝试后,我发现 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