简体   繁体   English

Android - 自定义按钮,具有形状可绘制和渐变编程

[英]Android - custom button with shape drawables and a gradient programmatically

I want to make a custom button like this program does with perhaps a radial gradient. 我想制作一个像这个程序的自定义按钮,也许是一个径向渐变。

I subclassed view, and draw three shape drawables and then draw the text. 我将视图子类化,并绘制三个形状drawable,然后绘制文本。 the text seems off center, so I tried to draw a bounding rectangle for the text, but no luck there. 文本似乎偏离中心,所以我试图为文本绘制一个边界矩形,但那里没有运气。 and was planning on adding a click listener to get button like behaviour. 并计划添加一个点击监听器来获取类似行为的按钮。

perhaps I should subclass button, but where to draw my drawables so they don't get messed up by the button's text being drawn. 也许我应该是子类按钮,但是在哪里绘制我的drawables所以他们不会被按钮的文本绘制搞乱。

Any pointers will be appreciated. 任何指针将不胜感激。

thanks 谢谢

Edit2: see second attempt below. Edit2:看下面的第二次尝试。

Edit3: the reason for the bounty is to figure out why subclassing drawable does not work. Edit3:赏金的原因是弄清楚为什么子类化drawable不起作用。 the gradient is not so important. 梯度不是那么重要。

edit4: discovered drawRect before getTextBounds() in DrawableView::OnDraw(). edit4:在DrawableView :: OnDraw()中的getTextBounds()之前发现了drawRect。

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};
}

This version tries to subclass drawable and use a button. 此版本尝试子类drawable并使用按钮。 but the button's drawing seems to interfere with drawing my drawable shapes. 但是按钮的绘图似乎会干扰我绘制可绘制的形状。 it looks like the bounds are being ignored. 看起来边界被忽略了。

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) Use alignment to draw text at the center in your DrawableView (should help with the text seems off center ): 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) To answer your question the reason for the bounty is to figure out why subclassing drawable does not work : 2)回答你的问题,赏金的原因是弄清楚为什么子类化drawable不起作用

I suppose it's because you create DrawableView in MyDrawable and don't add it to any container which means you don't measure and layout it. 我想这是因为你在MyDrawable创建DrawableView并且不将它添加到任何容器中,这意味着你不测量和布局它。 So, it's probably zero height and width. 所以,它的高度和宽度可能都是零。

3) I would suggest you to use Button instead of custom views and drawables. 3)我建议你使用Button而不是自定义视图和drawables。 You extend from Button and do additional drawings in the end of onDraw method, something like this: 您从Button扩展并在onDraw方法的末尾执行其他绘图,如下所示:

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

Original Incorrect Answer 原始不正确的答案

the reason for the bounty is to figure out why subclassing drawable does not work 赏金的原因是弄清楚为什么子类化drawable不起作用

Try to check if you need to call: 尝试检查是否需要致电:

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

use this code to make gradient button 使用此代码制作渐变按钮

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);

It is not a good idea to create Drawable depended on a View. 创建依赖于视图的Drawable并不是一个好主意。 As Eugen Pechanec suggested, make MyDrawable and DrawableView static. 正如Eugen Pechanec建议的那样,将MyDrawable和DrawableView设为静态。

You are using ShapeDrawables only in MyDrawable, so you can move it from DrawableView. 您只在MyDrawable中使用ShapeDrawables,因此您可以从DrawableView移动它。

It can be something like this: 它可以是这样的:

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);
    }
}

You can consider to not use ShapeDrawable and draw the shapes by yourself: 您可以考虑不使用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);
    }
}

By the way, it is good to use StateListDrawable for a Button. 顺便说一句,最好将StateListDrawable用于Button。
So you can use MyDrawable like this: 所以你可以像这样使用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);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM