使用自定义形状项的RecyclerView

10

我创建了一个自定义形状的ImageView。如果您在ScrollView中使用它,它可以正常工作。但是当我尝试在RecyclerView中使用它时,我观察到了奇怪的行为。图像没有得到绘制并显示间隙(请参见第一张图片),除非您向下滚动(请参见第二张图片)。当您向上滚动时,同样的事情也会发生。

我想知道如何避免这些间隙。请您指点我做错了什么?感谢您的帮助。

滚动前或向上滚动后的初始状态:

Initial state

向下滚动后:

After scrolling down

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

主活动界面对应的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"/>
2个回答

9

经过一些研究和尝试,我发现路径值在边框方面并不正确和良好设计。有些情况下它们会重叠在一起,我认为这会导致图像无法正确绘制。

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

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


1

在这里,你应该使用切割布局(cut layout)来处理行项目的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" />


1
感谢您的评论。是的,我知道这个库。但正如您从我提供的截图中所看到的,recyclerview项目彼此重叠,并且所有项目都有自己的分隔符(橙色线)。并且imageview的裁剪路径和分隔符路径通过重叠高度而不是角度计算,以确保每个项目尊重其边界并正确绘制。我只是想知道我的视图或recyclerview实现有什么问题。但是还是谢谢您的建议。 - Fatih Santalu
这个库也存在同样的问题。尝试在RecyclerView中使用它。 - Fatih Santalu
你设置了对角线位置吗?在XML中应该像这样:diagonal:diagonal_position="bottom"。我不确定,但如果你检查最后一个索引,那么你可以为它使用不同的布局,在其中对角线位置设置为底部。 - Farhana Naaz Ansari
你可以使用不同的布局,也可以使用相同的布局,但是你可以控制布局的可见性,并将其放置在最后一个索引位置。 - Farhana Naaz Ansari
你有一个库,可以在适配器类的最后一个索引处设置对角线属性。 - Farhana Naaz Ansari
是的,我定义了位置。我按照库建议使用它。很遗憾你不能以编程方式设置这些属性(位置、方向等),因此必须为此定义不同的xml文件。通过在适配器中使用不同的视图类型来考虑视图位置,我们可以在某种程度上实现我想要的效果。但正如我之前所说的那样,我们还需要创建边框并计算间隙大小以调整项目重叠(负上边距)。无论如何,就像我在先前的评论中所说的那样,我只是想知道我的实现有什么问题。再次感谢您的帮助。 - Fatih Santalu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接