2021年4月29日星期四

delete (or make transparent) canvas area enclosed by a path

my goal is to support a design that has a top bar made of a custom view with the bottom edge defined by two bezier curves. i have extended ImageView and defined a path that encompasses an area that I would like to delete (or make transparent). I'm new to working with graphics in android; is what i'm trying to do even possible? here is what i have done so far:

current progress

here is the class:

import android.app.Activity;  import android.content.Context;  import android.graphics.Canvas;  import android.graphics.Matrix;  import android.graphics.Paint;  import android.graphics.Path;  import android.graphics.PointF;  import android.graphics.drawable.Drawable;  import android.util.AttributeSet;  import android.util.DisplayMetrics;  import android.util.Log;    import androidx.annotation.Nullable;    public class BezierCroppedImageView extends androidx.appcompat.widget.AppCompatImageView {      private static final String LOG_TAG = BezierCroppedImageView.class.getSimpleName();      Paint mPaint;      Path mPath;        PointF[] points = new PointF[8];      DisplayMetrics dm = new DisplayMetrics();        public BezierCroppedImageView(Context context, @Nullable AttributeSet attrs) {          super(context, attrs);        }        protected void onDraw(Canvas canvas) {          Log.w(LOG_TAG, "drawing bezier");          super.onDraw(canvas);              mPaint.setStyle(Paint.Style.STROKE);          mPaint.setStrokeWidth((float) 2.5);          mPaint.setPathEffect(null);          canvas.drawPaint(mPaint);            mPath.moveTo(points[0].x, points[0].y);          mPath.quadTo(points[1].x, points[1].y, points[2].x, points[2].y);          mPath.lineTo(points[3].x, points[3].y);          mPath.quadTo(points[4].x, points[4].y, points[5].x, points[5].y);          mPath.lineTo(points[6].x, points[6].y);          mPath.lineTo(points[7].x, points[7].y);          mPath.close();            mPaint.setColor(getResources().getColor(android.R.color.holo_green_light));          canvas.drawPath(mPath, mPaint);      }        @Override      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {          super.onMeasure(widthMeasureSpec, heightMeasureSpec);            mPath = new Path();          mPaint = new Paint();            float[] f = new float[9];          getImageMatrix().getValues(f);          final float scaleX = f[Matrix.MSCALE_X];          final float scaleY = f[Matrix.MSCALE_Y];            final Drawable d = getDrawable();          final int origW = d.getIntrinsicWidth();          final int origH = d.getIntrinsicHeight();            final int actW = Math.round(origW * scaleX);          final int actH = Math.round(origH * scaleY);            ((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(dm);          Log.w(LOG_TAG, "measuring bezier!!; w/h: "+dm.widthPixels+"/"+dm.heightPixels);          Log.w(LOG_TAG, "orig w/h: "+origW+"/"+origH+"; actual w/h: "+actW+"/"+actH+"; scale w/h: "+scaleX+"/"+scaleY);          float width = dm.widthPixels;          float height = dm.heightPixels;          points[0] = new PointF(0, (float) (0.3 * height));          points[1] = new PointF(0, (float) (0.2 * height));          points[2] = new PointF((float) (0.1 * height), (float) (0.2 * height));          points[3] = new PointF((float) (width - 0.1 * height), (float) (0.2 * height));          points[4] = new PointF(width, (float) (0.2 * height));          points[5] = new PointF(width, (float) (0.1 * height));          points[6] = new PointF(width, height);          points[7] = new PointF(0, height);          setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);      }  }

and here is the layout file

<?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"      android:layout_width="match_parent"      android:layout_height="match_parent">        <androidx.constraintlayout.widget.Guideline          android:layout_width="match_parent"          android:layout_height="wrap_content"          android:orientation="horizontal"          app:layout_constraintGuide_begin="90dp"          android:id="@+id/header_guide_top"/>        <androidx.constraintlayout.widget.Guideline          android:layout_width="match_parent"          android:layout_height="wrap_content"          android:orientation="horizontal"          app:layout_constraintGuide_begin="120dp"          android:id="@+id/header_guide_bot"/>        <androidx.swiperefreshlayout.widget.SwipeRefreshLayout          xmlns:android="http://schemas.android.com/apk/res/android"          android:id="@+id/swiperefresh"          app:layout_constraintTop_toBottomOf="@id/header_guide_top"          app:layout_constraintStart_toStartOf="parent"          app:layout_constraintEnd_toEndOf="parent"          android:layout_width="match_parent"          android:layout_height="wrap_content">            <androidx.coordinatorlayout.widget.CoordinatorLayout              android:id="@+id/coordinator_layout"              android:layout_width="match_parent"              android:layout_height="match_parent">                <FrameLayout                  android:id="@+id/frame_layout"                  android:layout_width="match_parent"                  android:layout_height="match_parent"                  app:layout_behavior="@string/appbar_scrolling_view_behavior"/>          </androidx.coordinatorlayout.widget.CoordinatorLayout>      </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>        <com.collinsresearch.flameboss.BezierCroppedImageView          android:onClick="bezierClicked"          app:layout_constraintStart_toStartOf="parent"          app:layout_constraintEnd_toEndOf="parent"          app:layout_constraintTop_toTopOf="parent"          android:id="@+id/bezier_test_image"          android:layout_width="match_parent"          android:layout_height="match_parent"          android:src="@drawable/pexels"          android:scaleType="centerCrop"/>        <com.google.android.material.bottomnavigation.BottomNavigationView          android:id="@+id/navigation"          android:layout_width="match_parent"          android:layout_height="wrap_content"          app:layout_constraintBottom_toBottomOf="parent"          app:layout_constraintStart_toStartOf="parent"          app:layout_constraintEnd_toEndOf="parent"          android:background="@android:color/holo_green_light"          app:itemIconTint="@color/ic_on"          app:itemTextColor="@color/bottom_navigation_view_selector"          app:itemIconSize="@dimen/nav_ic_size"          app:menu="@menu/navigation"          android:visibility="visible"          android:translationY="25dp"/>    </androidx.constraintlayout.widget.ConstraintLayout>

as you can see, my strategy is to use a ConstraintLayout that contains the BottomNavigationView, a SwipeRefreshLayout that will contain each fragment, and put my BezierCroppedImageView constrained to the top with a transparent area exposing the SwipeRefreshLayout. I also plan to add some buttons to the top view but I'm having trouble with the transparency.

I've been looking at examples and documentation but I haven't found anything helpful. I looked into canvas.clipToPath but that's only supported after API level 28 and only supports oval and rectangular paths. I've also taken a look at the different PorterDuff modes but I'm not even sure if that's relevant here.

I was only able to get this far using this documentation https://developer.android.com/guide/topics/ui/custom-components and various stackoverflow articles. I appreciate any thoughts, comments, or thought-provoking questions. Thanks in advance for you time!!

https://stackoverflow.com/questions/67327687/delete-or-make-transparent-canvas-area-enclosed-by-a-path April 30, 2021 at 11:06AM

没有评论:

发表评论