RecyclerView与自定义形状的项目

时间:2017-07-11 07:08:32

标签: android canvas android-recyclerview imageview shape

我创建了一个自定义形状的imageview。如果你在scrollview中使用它,它工作正常。但当我试图在回收者视图中使用它时,我发现了一种奇怪的行为。除非向下滚动(见第2张图像),否则图像不会得到绘图并显示间隙(参见第1张图像)。向上滚动时会发生同样的事情。

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

初始状态或向上滚动后:

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

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

2 个答案:

答案 0 :(得分:8)

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

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

/**
 * 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 :(得分:1)

你应该对行项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" />