1

I'm trying to draw an arc from one view to another (canvas drawing) inside recyclerview. So I added a custom view in recyclerview layout. But it's not getting drawn properly. It draws randomly for some items in recyclerview. Here's my code :

ArcView

public class ArcView extends View {

    final RectF oval = new RectF();
    Path myPath = new Path();
    Paint paint;
    float left, top, right, bottom;

    /**/
    private float mCanvasCenterX;

    private float mCenterCircleWidth, mCenterCircleHeight;

    /**/
    public ArcView(Context context) {
        super(context);
    }
    public ArcView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }
    public ArcView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs);
    }

    private void init(AttributeSet set) {
        if (set == null)
            return;
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        /*TypedArray typedArray = getContext().obtainStyledAttributes(set, R.styleable.FlightStatusArcView);
        typedArray.recycle();*/
    }

    public void setArcProperties(float l, float t, float r, float b) {
        left = l;
        top = t;
        right = r;
        bottom = b;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        oval.set(left, top, right, bottom);
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(5f);

        canvas.drawArc(oval, 180f, 180f, false, paint);
        drawArc(canvas);




    }



    private void drawArc(Canvas canvas) {

        canvas.save();

        Paint p = new Paint();
        p.setColor(Color.WHITE);
        p.setStyle(Paint.Style.FILL);
        p.setStrokeWidth(4f);

        float startX = oval.left + 20;
        float startY = oval.top + (oval.centerY() - oval.top)/ 2;

        Path path = new Path();
        path.moveTo(startX, startY);
        path.lineTo(startX + 20, startY + 20);
        path.lineTo(startX + 20, startY);
        path.lineTo(startX + 20, startY);
        path.close();

        canvas.drawPath(path, p);

        canvas.restore();
    }


}

activity_main, layout of MainActivity which holds the recyclerview

<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>


    <androidx.recyclerview.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/recycler_view"
            android:paddingEnd="5dp"
            android:paddingStart="5dp"
            android:scrollbars="vertical">

    </androidx.recyclerview.widget.RecyclerView>

</androidx.constraintlayout.widget.ConstraintLayout>

MyAdapter , adapter class for recyclerview. Here, I'm trying to draw the canvas.But it doesn't draw anything.

class MyAdapter(var items: ArrayList<Item>) :
    RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
    fun updateItems(newItems: List<Item>) {
        items.clear()
        items.addAll(newItems)
        notifyDataSetChanged()
    }
    override fun onCreateViewHolder(parent: ViewGroup, p1: Int) = MyViewHolder(
        LayoutInflater.from(parent.context).inflate(R.layout.activity_arc_view, parent, false)
    )
    override fun getItemCount() = items.size
    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.bind(items[position])
        val handler = Handler()
        handler.postDelayed(Runnable {
            setPropertiesAndDrawArc(holder)
        }, 1500)
    }
    class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val from = view.from_airport
        val to = view.to_airport
        val customView = view.customView
        val circleStart = view.circle_start
        val circleEnd = view.circle_end
        val inFlight = view.in_flight
        val flyingTime = view.flying_time
        fun bind(item: Item) {
            from.text = item.from
            to.text = item.to
        }
    }
    private fun setPropertiesAndDrawArc(holder: MyViewHolder) {
        val circleStartLocation = IntArray(2)
        holder.circleStart.getLocationOnScreen(circleStartLocation)
        val circleEndLocation = IntArray(2)
        holder.circleEnd.getLocationOnScreen(circleEndLocation)
        val inFlightLocation = IntArray(2)
        holder.inFlight.getLocationOnScreen(inFlightLocation)
        val fromAirportLocation = IntArray(2)
        holder.from.getLocationOnScreen(fromAirportLocation)
        val flyingTimeLocation = IntArray(2)
        holder.flyingTime.getLocationOnScreen(flyingTimeLocation)
        val toAirportLocation = IntArray(2)
        holder.to.getLocationOnScreen(toAirportLocation)
        holder.flyingTime.measure(0, 0)
        val flyingTimeHeight = holder.flyingTime.getMeasuredHeight()
        val flyingTimeTop = flyingTimeLocation[1] - flyingTimeHeight
        val left = circleStartLocation[0] + holder.circleStart.getWidth() / 2
        val top = inFlightLocation[1] + (flyingTimeTop - inFlightLocation[1]) / 2
        val right = circleEndLocation[0] + holder.circleEnd.getWidth() / 2
        val bottom = circleStartLocation[1] + (circleStartLocation[1] - inFlightLocation[1]) / 2
        holder.customView.setArcProperties(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat())
        holder.customView.invalidate()
    }
}

activity_arc_view, the recyclerview item row layout

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <com.example.customviewinrv.ArcView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/customView"
        android:elevation="2dp"
        android:layout_marginTop="0dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>


    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="170dp"
        android:background="@color/colorPrimaryDark"
        android:visibility="visible"
        android:layout_marginTop="20dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <View
            android:id="@+id/circle_start"
            android:layout_width="12dp"
            android:layout_height="12dp"
            android:background="@drawable/circle_white"
            app:layout_constraintBottom_toTopOf="@id/from_airport"
            app:layout_constraintEnd_toEndOf="@id/from_airport"
            app:layout_constraintStart_toStartOf="@id/from_airport" />

        <View
            android:id="@+id/circle_end"
            android:layout_width="12dp"
            android:layout_height="12dp"
            android:background="@drawable/circle_white"
            app:layout_constraintBottom_toTopOf="@id/to_airport"
            app:layout_constraintEnd_toEndOf="@id/to_airport"
            app:layout_constraintStart_toStartOf="@id/to_airport" />


        <TextView
            android:id="@+id/from_airport"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:gravity="center"
            android:text="Trivandrum\nInternational Airport"
            android:textColor="#FFFFFF"
            android:textSize="19sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent" />

        <TextView
            android:id="@+id/to_airport"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:gravity="center"
            android:text="Mineralnye Vody \nAirport"
            android:textColor="#FFFFFF"
            android:textSize="19sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintRight_toRightOf="parent" />

        <TextView
            android:id="@+id/in_flight"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:gravity="center"
            android:text="In-flight"
            android:textColor="#FFFFFF"
            android:textSize="19sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/flying_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:gravity="center"
            android:text="Flying Time\n03h 30m"
            android:textColor="#FFFFFF"
            android:textSize="11sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/in_flight" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

It works well if it's on a normal activity without recyclerview. I wonder how do I achieve it inside recyclerview. Please help.

Rakesh
  • 1,205
  • 1
  • 14
  • 33
  • Seem like I have same issue, custom view using canvas to draw inside recyclerview does not display properly. Still has not clue of root issue. – Nezneika Oct 19 '20 at 15:25

2 Answers2

0

I don't see a super call to onDraw function, do this:

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
....
....
...

update

give your custom view a height and width, instead of wrap_content:

<com.example.customviewinrv.ArcView
    android:layout_width="....."
    android:layout_height="......"
Hasan Bou Taam
  • 4,017
  • 2
  • 12
  • 22
0

I understood what you are trying to do but your code is too complicated to understand but your approach is not true. First of all you should use onSizeChanged() callback in your view to understand change in the size so you can draw your arc properly. Think like this constraint layout adjust your view size according to the constraint you define(from_airport and to airport). So your view's width and height adjust properly so you can make required calculations in this callback then you can make your draw operation according to values you have just calculated in onSizeChanged so you even don't need to call invalidate because the system automatically call your view to redraw. Another option is to use a custom view group but for your situation it is unnecessary.

M.ekici
  • 765
  • 4
  • 10