Android - настраиваемая кнопка с возможностью рисования фигур и градиентом программно

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

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

Возможно, мне нужна кнопка подкласса, но где рисовать мои рисунки, чтобы они не перепутались с текстом кнопки.

Любые указатели будут оценены.

спасибо

Edit2: см. вторую попытку ниже.

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

edit4: обнаружено drawRect перед getTextBounds() в DrawableView:: OnDraw().

package acme.drawables;
import android.content.*;
import android.content.pm.*;
import android.graphics.*;
import android.graphics.drawable.*;
import android.graphics.drawable.shapes.*;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.*;
import android.view.Menu;
import android.view.View;
import android.widget.*;
import static java.lang.Math.*;
public class MainActivity extends AppCompatActivity {
    DrawableView drawableView;
    LinearLayout row(boolean isRow1) {
        LinearLayout layout=new LinearLayout(this);
        layout.setOrientation(LinearLayout.HORIZONTAL);
        LinearLayout.LayoutParams layoutParams=new LinearLayout.LayoutParams(w,d);
        int m=(int)round(w*margin);
        layoutParams.setMargins(m,m,m,m);
        for(int i=0;i<n;i++)
            layout.addView(drawableView=new DrawableView(this,i,isRow1),layoutParams);
        return layout;
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        DisplayMetrics metrics=new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        w=d=(int)round(metrics.densityDpi);
        LinearLayout row1=row(true);
        LinearLayout row2=row(false);
        LinearLayout layout=new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);
        layout.addView(row1);
        layout.addView(row2);
        LinearLayout l=new LinearLayout(this);
        setContentView(layout);
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        return true;
    }
    public class DrawableView extends View {
        public DrawableView(Context context,int column,boolean isRow1) {
            super(context);
            setBackgroundColor(Color.TRANSPARENT);
            this.column=column;
            text=""+(char)('0'+column);
            int r=(int)round(w*radius);
            d0=new ShapeDrawable(new RoundRectShape(new float[]{r,r,r,r,r,r,r,r},null,null));
            d0.getPaint().setColor(0xff000000);
            d0.setBounds(0,0,w,d);
            d1=new ShapeDrawable(new RoundRectShape(new float[]{r,r,r,r,r,r,r,r},null,null));
            d1.getPaint().setColor(on[column]);
            d1.setBounds(edge,edge,w-edge,d-edge);
            d2=new ShapeDrawable(new RoundRectShape(new float[]{r,r,r,r,r,r,r,r},null,null));
            int b=(int)round(w*border);
            d2.setBounds(b/2,b/2,w-b/2,d-b/2);
            d2.getPaint().setColor(isRow1?on[column]:off[column]);
        }
        protected void onDraw(Canvas canvas) {
            d0.draw(canvas);
            d1.draw(canvas);
            d2.draw(canvas);
            Paint paint = new Paint();
            //paint.setColor(Color.WHITE);
            paint.setStyle(Paint.Style.FILL);
            //canvas.drawPaint(paint);
            paint.setColor(Color.CYAN);
            paint.setTextSize(w*95/100);
            Rect r=new Rect();
            paint.getTextBounds(text,0,1,r); // were switched
            canvas.drawRect(r,paint);  // were switched
            int x=(w-r.width())/2,y=(d-r.height())/2;
            paint.setColor(Color.BLACK);
            canvas.drawText(text,x,d-y,paint);
        }
        final int column;
        final String text;
        ShapeDrawable d0, d1, d2;
    }
    final int n=5, edge=1;
    double margin=.10, radius=.05, border=.15;
    int w, d;
    final int[] on=new int[]{0xffff0000,0xffffff00,0xff00ff00,0xff0000ff,0xffff8000};
    final int[] off=new int[]{0xff800000,0xff808000,0xff008000,0xff000080,0xff804000};
}

Эта версия пытается подклассировать и использовать кнопку. но рисование кнопок, похоже, мешает рисовать мои рисунки. похоже, что оценки игнорируются.

package acme.drawables;
import android.content.*;
import android.content.pm.*;
import android.graphics.*;
import android.graphics.drawable.*;
import android.graphics.drawable.shapes.*;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.*;
import android.view.Menu;
import android.view.View;
import android.widget.*;

import static java.lang.Math.*;
public class MainActivity extends AppCompatActivity {
    LinearLayout row(boolean isRow1) {
        LinearLayout layout=new LinearLayout(this);
        layout.setOrientation(LinearLayout.HORIZONTAL);
        LinearLayout.LayoutParams layoutParams=new LinearLayout.LayoutParams(w,d);
        int m=(int)round(w*margin);
        layoutParams.setMargins(m,m,m,m);
        if(true)
            for(int i=0;i<n;i++) { // subclass drawable
                Button b=new Button(this);
                b.setText(""+(char)('0'+i));
                b.setBackground(new MyDrawable(i,i/n%2==0));
                layout.addView(b,layoutParams);
            }
        else
            for(int i=0;i<n;i++) // use drawable view with canvas draw text
                layout.addView(drawableView=new DrawableView(i,isRow1),layoutParams);
        return layout;
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        DisplayMetrics metrics=new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        w=d=(int)round(metrics.densityDpi);
        LinearLayout row1=row(true);
        LinearLayout row2=row(false);
        LinearLayout layout=new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);
        layout.addView(row1);
        layout.addView(row2);
        LinearLayout l=new LinearLayout(this);
        setContentView(layout);
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        return true;
    }
    public class MyDrawable extends Drawable {
        public MyDrawable(int column,boolean isRow1) {
            drawableView=new DrawableView(column,isRow1);
        }
        public void setAlpha(int alpha) {
            System.out.println("ignoring set alpha to: "+alpha);
        }
        public void setColorFilter(ColorFilter colorFilter) {
            System.out.println("ignoring set color filter to: "+colorFilter);
        }
        public int getOpacity() {
            return PixelFormat.OPAQUE;
        }
        public void draw(Canvas canvas) {
            System.out.println(this+" is drawing.");
            drawableView.d0.draw(canvas);
            System.out.println("d0 bounds: "+drawableView.d0.getBounds());
            drawableView.d1.draw(canvas);
            System.out.println("d1 bounds: "+drawableView.d1.getBounds());
            drawableView.d2.draw(canvas);
            System.out.println("d2 bounds: "+drawableView.d2.getBounds());
        }
        final DrawableView drawableView; // cheat by delegating
    }
    public class DrawableView extends View {
        public DrawableView(int column,boolean isRow1) {
            super(MainActivity.this);
            setBackgroundColor(Color.TRANSPARENT);
            this.column=column;
            text=""+(char)('0'+column);
            int r=(int)round(w*radius);
            d0=new ShapeDrawable(new RoundRectShape(new float[]{r,r,r,r,r,r,r,r},null,null));
            d0.getPaint().setColor(0xff000000);
            d0.setBounds(0,0,w,d);
            d1=new ShapeDrawable(new RoundRectShape(new float[]{r,r,r,r,r,r,r,r},null,null));
            d1.getPaint().setColor(on[column]);
            d1.setBounds(edge,edge,w-edge,d-edge);
            d2=new ShapeDrawable(new RoundRectShape(new float[]{r,r,r,r,r,r,r,r},null,null));
            int b=(int)round(w*border);
            d2.setBounds(b/2,b/2,w-b/2,d-b/2);
            d2.getPaint().setColor(isRow1?on[column]:off[column]);
        }
        protected void onDraw(Canvas canvas) {
            d0.draw(canvas);
            d1.draw(canvas);
            d2.draw(canvas);
            Paint paint=new Paint();
            //paint.setColor(Color.WHITE);
            paint.setStyle(Paint.Style.FILL);
            //canvas.drawPaint(paint);
            paint.setColor(Color.CYAN);
            paint.setTextSize(w*95/100);
            Rect r=new Rect();
            canvas.drawRect(r,paint);
            paint.getTextBounds(text,0,1,r);
            int x=(w-r.width())/2, y=(d-r.height())/2;
            paint.setColor(Color.BLACK);
            canvas.drawText(text,x,d-y,paint);
        }
        final int column;
        final String text;
        ShapeDrawable d0, d1, d2;
    }
    DrawableView drawableView;
    final int n=5, edge=1;
    double margin=.10, radius=.05, border=.15;
    int w, d;
    final int[] on=new int[]{0xffff0000,0xffffff00,0xff00ff00,0xff0000ff,0xffff8000};
    final int[] off=new int[]{0xff800000,0xff808000,0xff008000,0xff000080,0xff804000};
}

Ответы

Ответ 1

1) Используйте выравнивание, чтобы нарисовать текст в центре в DrawableView (следует помочь, если текст кажется неактивным):

paint.setTextAlign(Align.CENTER); // <- should help you with centering
paint.getTextBounds(text, 0, 1, r);
int x = w / 2, y = (d - r.height()) / 2; // <- was updated too

2). Чтобы ответить на ваш вопрос, причина щедрости заключается в том, чтобы выяснить, почему подклассификация не работает:

Я предполагаю это, потому что вы создаете DrawableView в MyDrawable и не добавляете его в какой-либо контейнер, что означает, что вы не измеряете и не планируете его. Таким образом, это, вероятно, нулевая высота и ширина.

3) Я бы предложил вам использовать Button вместо пользовательских представлений и чертежей. Вы выходите из Button и делаете дополнительные чертежи в конце метода onDraw, примерно так:

public void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // your custom drawing over button
}

Оригинальный неверный ответ

причина щедрости заключается в том, чтобы выяснить, почему подклассы drawable не работают

Попробуйте проверить, нужно ли вам звонить:

  • super.onDraw(canvas) в DrawableView.onDraw
  • super.draw(canvas) в MyDrawable.draw

Ответ 2

используйте этот код, чтобы сделать кнопку градиента

Button your_button= findViewById(R.id.button);

    GradientDrawable gd = new GradientDrawable(
            GradientDrawable.Orientation.TOP_BOTTOM,
            new int[] {0xFF616261,0xFF131313});
    gd.setCornerRadius(0f);

    your_button.setBackgroundDrawable(gd);

Ответ 3

Не рекомендуется создавать Drawable в зависимости от вида. Как предложил Евгений Печанец, сделайте MyDrawable и DrawableView static.

Вы используете ShapeDrawables только в MyDrawable, поэтому вы можете переместить его из DrawableView.

Это может быть примерно так:

public static class MyDrawable extends Drawable {
    private ShapeDrawable d0, d1, d2;
    private int edge;
    private int border;

    public MyDrawable(int color1, int color2, int radius, int edge, int border) {
        this.edge = edge;
        this.border = border;

        float[] outerRadii = new float[] {
                radius, radius,
                radius, radius,
                radius, radius,
                radius, radius
        };

        d0 = new ShapeDrawable(new RoundRectShape(outerRadii, null, null));
        d0.getPaint().setColor(0xff000000);
        d1 = new ShapeDrawable(new RoundRectShape(outerRadii, null, null));
        d1.getPaint().setColor(color1);
        d2 = new ShapeDrawable(new RoundRectShape(outerRadii, null, null));
        d2.getPaint().setColor(color2);
    }

    public void setAlpha(int alpha) {
        System.out.println("ignoring set alpha to: " + alpha);
    }

    public void setColorFilter(ColorFilter colorFilter) {
        System.out.println("ignoring set color filter to: " + colorFilter);
    }

    public int getOpacity() {
        return PixelFormat.OPAQUE;
    }

    public void draw(Canvas canvas) {
        System.out.println(this + " is drawing.");
        d0.draw(canvas);
        System.out.println("d0 bounds: " + d0.getBounds());
        d1.draw(canvas);
        System.out.println("d1 bounds: " + d1.getBounds());
        d2.draw(canvas);
        System.out.println("d2 bounds: " + d2.getBounds());
    }

    @Override
    public void setBounds(int left, int top, int right, int bottom) {
        super.setBounds(left, top, right, bottom);
        d0.setBounds(left, top, right, bottom);
        d1.setBounds(left + edge, top + edge, right - edge, bottom - edge);
        d2.setBounds(left + border / 2, top + border / 2,
                right - border / 2, bottom - border / 2);
    }
}

Вы можете не использовать ShapeDrawable и рисовать фигуры самостоятельно:

public static class MyDrawable extends Drawable {
    private int radius;
    private int edge;
    private int border;

    private RectF bounds1 = new RectF();
    private RectF bounds2 = new RectF();
    private RectF bounds3 = new RectF();

    private Paint paint1 = new Paint();
    private Paint paint2 = new Paint();
    private Paint paint3 = new Paint();

    public MyDrawable(int color1, int color2, int radius, int edge, int border) {
        this.radius = radius;
        this.edge = edge;
        this.border = border;

        float[] outerRadii = new float[] {
                radius, radius,
                radius, radius,
                radius, radius,
                radius, radius
        };

        paint1.setColor(0xff000000);
        paint2.setColor(color1);
        paint3.setColor(color2);
    }

    public void setAlpha(int alpha) {
        System.out.println("ignoring set alpha to: " + alpha);
    }

    public void setColorFilter(ColorFilter colorFilter) {
        System.out.println("ignoring set color filter to: " + colorFilter);
    }

    public int getOpacity() {
        return PixelFormat.OPAQUE;
    }

    public void draw(Canvas canvas) {
        canvas.drawRoundRect(bounds1, radius, radius, paint1);
        canvas.drawRoundRect(bounds2, radius, radius, paint2);
        canvas.drawRoundRect(bounds3, radius, radius, paint3);
    }

    @Override
    public void setBounds(int left, int top, int right, int bottom) {
        super.setBounds(left, top, right, bottom);

        bounds1.set(left, top, right, bottom);

        bounds2.set(bounds1);
        bounds2.inset(edge, edge);

        bounds3.set(bounds1);
        bounds3.inset(border / 2, border / 2);
    }
}

Кстати, полезно использовать StateListDrawable для кнопки.
Таким образом, вы можете использовать MyDrawable следующим образом:

MyDrawable drawable = new MyDrawable(...);
MyDrawable drawablePressed = new MyDrawable(...);
MyDrawable drawableFocused = new MyDrawable(...);

StateListDrawable stateDrawable = new StateListDrawable();
stateDrawable.addState(new int[]{android.R.attr.state_pressed}, drawablePressed);
stateDrawable.addState(new int[]{android.R.attr.state_focused}, drawableFocused);
stateDrawable.addState(new int[]{}, drawable);

Button button = (Button) findViewById(R.id.button);
button.setBackground(stateDrawable);