Как создать прямоугольник с закругленными углами с разными сечениями разных цветов

ТРЕБОВАНИЕ

Как мне создать вид, похожий на этот.

Я хотел бы нарисовать вид на экране, который представляет собой линию, разбитую на сегменты, показывающие значения в процентах от всего вида. Мои требования

  • вид имеет разные разделы, которые разных цветов
  • представление может отображать не все разделы, оно может иметь только первые 2 или первый и последний или только один цвет и т. д. - это известно только во время выполнения
  • размер различных разделов известен только во время выполнения, поэтому необходимо указывать программно
  • левый и правый углы всего вида закруглены

ИДЕИ / Вещи, которые я испытал

(1) Пользовательский вид рендеринга 3 прямоугольника рядом

Я попробовал пользовательский вид, который отображает 3 прямоугольника рядом. Но они, очевидно, имеют квадратные углы.

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

    int viewHeight = 50;

    canvas.drawrect(0,  0, 60,  viewHeight, paint); // A
    canvas.drawrect(60, 0, 120, viewHeight, paint); // B
    canvas.drawrect(120,0, 180, viewHeight, paint); // C
}

(2) Форма с закругленными углами

Я знаю, что могу использовать форму, чтобы определить прямоугольник со скругленными углами, используя следующее, но это один цвет.

<shape xmlns:android="http://schemas.android.com/apk/res/android">
     ...        
    <corners
        android:radius="4dp" />
    ....
</shape>

(3) Список слоев

Из прямоугольной формы Android с двумя разными цветами я вижу, что могу использовать список слоев, чтобы указать каждый элемент в форме, чтобы иметь разные цвета.

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">       

    <item>
        <shape android:shape="rectangle">
            <size
                android:width="40dp"
                android:height="40dp" />
            <solid android:color="#F86F05" />
        </shape>
    </item>

    <item android:top="10dp">
        <shape android:shape="rectangle">
            <size
                android:width="30dp"
                android:height="30dp" />
            <solid android:color="#B31F19" />
        </shape>
    </item>

</layer-list>

(4) Список слоев с углами??

Могу ли я добавить тег "углы" ко всему списку слоев, чтобы получить закругленные основные углы? Я предполагаю, что нет, и что угловая часть должна быть в тегах формы "Item".

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <corners
        android:radius="4dp" />

    <item>
        <shape android:shape="rectangle">
            <size
                android:width="40dp"
                android:height="40dp" />
            <solid android:color="#F86F05" />
        </shape>
    </item>

    <item android:top="10dp">
        <shape android:shape="rectangle">
            <size
                android:width="30dp"
                android:height="30dp" />
            <solid android:color="#B31F19" />
        </shape>
    </item>

</layer-list>

РЕЗЮМЕ

Этот последний приближается к моему требованию, однако

  • Как я могу указать ширину каждого "элемента" программно
  • как показать / скрыть "элемент" программно
  • как бы я закруглил только верхние наиболее видимые верхние углы "предмета" и нижние самые нижние углы "предмета"

ОБНОВЛЕНИЕ: КАК ДОБАВИТЬ ЛИФТ / СЕРУЮ ГРАНИЦУ

Спасибо "@0X0nosugar" за ваше решение. Теперь я хочу добавить высоту или небольшую серую границу, поскольку один из цветов очень светлый и близок к цвету фона. Когда я добавляю следующее, я получаю прямоугольную тень, которая выглядит ужасно с изогнутыми углами.

android:elevation="2dp"
android:outlineProvider="bounds"

Я хотел бы, чтобы это появилось как ниже

4 ответа

Решение

Вы можете создать кастом View который будет рисовать прямоугольники на обрезанной части Canvas:

public class RoundedCornersSegmentedView extends View {

    private Paint paintA, paintB, paintC;
    private float cornerRadius;
    private float measuredWidth, measuredHeight;
    private RectF rect = new RectF(0, 0, 0,0);
    private Path rectPath = new Path();

    public RoundedCornersSegmentedView(Context context) {
        super(context);
        init();
    }

    public RoundedCornersSegmentedView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public RoundedCornersSegmentedView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        setWillNotDraw(false);

        // add this so Canvas.clipPath() will give the desired result also for devices running Api level lower than 17,
        // see https://stackru.com/a/30354461/5015207
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            setLayerType(LAYER_TYPE_SOFTWARE, null);
        }
        paintA = new Paint(Paint.ANTI_ALIAS_FLAG);
        paintA.setColor(Color.GREEN);
        paintA.setStyle(Paint.Style.FILL);
        paintB = new Paint(Paint.ANTI_ALIAS_FLAG);
        paintB.setColor(Color.YELLOW);
        paintB.setStyle(Paint.Style.FILL);
        paintC = new Paint(Paint.ANTI_ALIAS_FLAG);
        paintC.setColor(Color.MAGENTA);
        paintC.setStyle(Paint.Style.FILL);

        // with  <dimen name="corner_radius">60dp</dimen> in res/values/dimens.xml
        cornerRadius = getResources().getDimensionPixelSize(R.dimen.corner_radius);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        measuredWidth = right - left;
        measuredHeight = bottom - top;
        rect.set(0, 0, measuredWidth, measuredHeight);
        rectPath.reset();
        rectPath.addRoundRect(rect, cornerRadius, cornerRadius, Path.Direction.CW);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.clipPath(rectPath);
        canvas.drawRect(0,0,measuredWidth/3f, measuredHeight, paintA);
        canvas.drawRect(measuredWidth/3f,0,2 * measuredWidth/3f, measuredHeight, paintB);
        canvas.drawRect(2 * measuredWidth/3f,0,measuredWidth, measuredHeight, paintC);
    }
}

Если вы хотите добавить какой-то полупрозрачный край, вы можете использовать Paint с прозрачным цветом и типом заполнения Paint.Style.STROKE и нарисуйте скругленный прямоугольник.

Paint shadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
// material color "Blue Gray 400",
// see https://material.io/design/color/the-color-system.html
shadowPaint.setColor(Color.argb(30, 120, 144, 156));
shadowPaint.setStyle(Paint.Style.STROKE);
shadowPaint.setStrokeWidth(30);

Прямоугольник (экземпляр вне onLayout() для лучшей производительности):

private RectF shadowRect = new RectF(0,0,0,0);

В onLayout():

int inset = 20;
shadowRect.set(inset, inset, measuredWidth - inset, measuredHeight - inset);

Вы должны переключить цвет / альфа-значение для тени Paint а также значения ширины обводки и вставки, пока вы не думаете, что это выглядит хорошо.

Применяется в onDraw() после того, как вы нарисовали цветные сегменты:

canvas.drawRoundRect(shadowRect, cornerRadius, cornerRadius, shadowPaint);

Это также может выглядеть красиво (больше 3D), если вы сложите полупрозрачный Paint s с уменьшением ширины обводки и увеличением вставки, как создание собственного цветового градиента.

Спасибо @wblaschko за то, что поделились фрагментом кода на ViewOutlineProvider! Я добавил его в свой пример и получил следующий эффект:

Изменения в моем коде (примечание: возможно только для уровня API 21+)

Внутренний класс пользовательских View:

@TargetApi(21)
static class ScalingOutlineProvider extends ViewOutlineProvider {
    private int cornerRadius;
    ScalingOutlineProvider(int cornerRadius){
        this.cornerRadius = cornerRadius;
    }
    @Override
    public void getOutline(View view, Outline outline) {
        outline.setRoundRect(0, 0, view.getWidth(), view.getHeight (), cornerRadius);
    }
}

И в конце init():

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
    // elevation of 4dp (cornerRadius was 60dp)
    setElevation(cornerRadius/15);
    setOutlineProvider(new ScalingOutlineProvider(cornerRadius));
}

Для дополнительного вопроса у вас есть два варианта. Используйте встроенные методы или нарисуйте свои (в зависимости от потребностей). Первый, скорее всего, правильный способ сделать это, если только вам не нужна собственная тень.

Встроенные методы

См. Раздел "Структура" в этом сообщении в блоге: https://android.jlelse.eu/mastering-shadows-in-android-e883ad2c9d5b

Пример кода из поста:

Создать OutlineProvider

public class ScalingLayoutOutlineProvider extends ViewOutlineProvider {

    @Override
    public void getOutline(View view, Outline outline) {
        outline.setRoundRect(0, 0, width, height, radius);
    }
}

Добавьте Outline Provider в свое представление

public class ScalingLayout extends FrameLayout {

    //...
    viewOutline = new ScalingLayoutOutlineProvider(w, h, currentRadius);
    setOutlineProvider(viewOutline);
    //..

}

Рисуя свой собственный

Для границы вы можете использовать View.getElevation(), чтобы получить желаемую высоту: https://developer.android.com/reference/android/view/View, а затем нарисовать другую фигуру за полосой для тени,

Вы должны использовать CardView взгляните на этот пример:

<androidx.cardview.widget.CardView
  android:layout_width="match_parent"
  android:layout_height="180dp"
  android:layout_margin="@dimen/activity_vertical_margin"
  app:cardPreventCornerOverlap="false"
  app:cardCornerRadius="@dimen/activity_vertical_margin"
  android:foreground="?android:attr/selectableItemBackground">

  <androidx.constraintlayout.widget.ConstraintLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content">

      <View
        android:id="@+id/view1"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#0c21a9"
        app:layout_constraintWidth_percent="0.33"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

      <View
        android:id="@+id/view2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#51c914"
        app:layout_constraintWidth_percent="0.33"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@id/view1" />

     <View
        android:id="@+id/view3"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#a90c0c"
        app:layout_constraintWidth_percent="0.33"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@id/view2" />     

  </androidx.constraintlayout.widget.ConstraintLayout>     

</androidx.cardview.widget.CardView> 

Это свойство предотвращает перекрытие в закругленных углах app:cardPreventCornerOverlap="false"

Обратите внимание, что я использую lib androidx рекомендовано Google AndroidX. Если вы предпочитаете, вы можете использовать устаревшую библиотеку поддержки.

Например (настроить цвета на желаемые, поля, высоты и т. Д.):

Вы можете создать это, используя LinearLayout как корневой макет с horizontal ориентация и назначение некоторых weights создавать дочерние макеты и добавлять к ним пользовательские фоны. Простой, не слишком много вложенных макетов.

Например:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:layout_margin="20dp"
    android:orientation="horizontal">

    <LinearLayout
        android:id="@+id/statsLayout"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:clickable="true"
        android:background="@drawable/left_background">

        <TextView
            android:id="@+id/stats"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="A%"
            android:textAppearance="@style/TextAppearance.AppCompat.Title"
            android:textColor="@android:color/black" />
    </LinearLayout>


    <LinearLayout
        android:id="@+id/ncaaInfoLayout"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:clickable="true"
        android:background="@drawable/middle_background">

        <TextView
            android:id="@+id/ncaaInfo"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="B%"
            android:textAppearance="@style/TextAppearance.AppCompat.Title"
            android:textColor="@android:color/black" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/accountLayout"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:clickable="true"
        android:background="@drawable/right_background">

        <TextView
            android:id="@+id/account"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="C%"
            android:textAppearance="@style/TextAppearance.AppCompat.Title"
            android:textColor="@android:color/black" />
    </LinearLayout>
</LinearLayout>

Чем создать фон для каждого ребенка LinearLayout для левого left_bacground.xml:

<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
    android:bottomLeftRadius="30dp"
    android:topLeftRadius="30dp" />
<solid android:color="@android:color/holo_green_dark" />

Чем для LinearLayout in middle middle_background.xml`:

<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/holo_orange_dark" />

И последний LinearLayout за право right_bacgkround.xml:

  <?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
    android:bottomRightRadius="30dp"
    android:topRightRadius="30dp" />
<solid android:color="@android:color/holo_blue_dark" />

Другие вопросы по тегам