1. ホーム
  2. android

[解決済み] マルチグラディエントシェイプ

2022-04-20 13:12:40

質問

以下の画像のような形状を作りたいのですが、どうすればいいですか?

上半分は色1から色2へのグラデーション、下半分は色3から色4へのグラデーションであることに注意してください。 1つのグラデーションを持つシェイプを作る方法は知っていますが、シェイプを2つに分割し、2つの異なるグラデーションを持つ1つのシェイプを作る方法はわかりません。

何かアイデアはありますか?

解決方法は?

XMLでこれを行うことはできないと思いますが(少なくともAndroidでは)、良い解決策を見つけましたので掲載します。 ここで というのは、とても助かりそうですね。

ShapeDrawable.ShaderFactory sf = new ShapeDrawable.ShaderFactory() {
    @Override
    public Shader resize(int width, int height) {
        LinearGradient lg = new LinearGradient(0, 0, width, height,
            new int[]{Color.GREEN, Color.GREEN, Color.WHITE, Color.WHITE},
            new float[]{0,0.5f,.55f,1}, Shader.TileMode.REPEAT);
        return lg;
    }
};

PaintDrawable p=new PaintDrawable();
p.setShape(new RectShape());
p.setShaderFactory(sf);

基本的には、int配列で複数のカラーストップを選択し、次のfloat配列でそれらのストップの位置(0から1まで)を定義します。そして、前述のように、これを標準的なDrawableとして使用することができます。

編集:あなたのシナリオでこれをどのように使うことができるかを紹介します。例えば、次のようなXMLで定義されたボタンがあるとします。

<Button
    android:id="@+id/thebutton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Press Me!"
    />

そして、onCreate()メソッドに次のような内容を記述します。

Button theButton = (Button)findViewById(R.id.thebutton);
ShapeDrawable.ShaderFactory sf = new ShapeDrawable.ShaderFactory() {
    @Override
    public Shader resize(int width, int height) {
        LinearGradient lg = new LinearGradient(0, 0, 0, theButton.getHeight(),
            new int[] { 
                Color.LIGHT_GREEN, 
                Color.WHITE, 
                Color.MID_GREEN, 
                Color.DARK_GREEN }, //substitute the correct colors for these
            new float[] {
                0, 0.45f, 0.55f, 1 },
            Shader.TileMode.REPEAT);
         return lg;
    }
};
PaintDrawable p = new PaintDrawable();
p.setShape(new RectShape());
p.setShaderFactory(sf);
theButton.setBackground((Drawable)p);

今はテストできないので、これは私の頭の中のコードですが、基本的には必要な色を置き換えたり、ストップを追加したりするだけです。基本的に、私の例では、明るい緑から始まり、中心より少し手前で白にフェードし(厳しい移行ではなく、フェードを与えるため)、45%から55%の間で白から中間の緑にフェードし、55%から最後までは中間の緑から暗い緑にフェードします。これは、あなたの図形と全く同じには見えないかもしれませんが(今、私はこれらの色をテストする方法がありません)、あなたの例を再現するためにこれを修正することができます。

編集:また 0, 0, 0, theButton.getHeight() は、グラデーションの x0, y0, x1, y1 座標を指します。つまり、基本的には、x = 0(左側)、y = 0(上側)から始まり、x = 0(垂直方向のグラデーションにしたいので、左右の角度は必要ない)、y = ボタンの高さまで伸びる。つまり、グラデーションはボタンの上部から下部まで90度の角度で進むことになります。

編集部:そうですか、もうひとつ、うまくいくアイデアがあるんです、ハハ。今はXMLで動いていますが、Javaのシェイプでも可能なはずです。これはちょっと複雑で、1つのシェイプに単純化する方法があると思うのですが、今のところ、これが私の持っているものです。

緑_水平_グラデーション.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle"
    >
    <corners
        android:radius="3dp"
        />
    <gradient
        android:angle="0"
        android:startColor="#FF63a34a"
        android:endColor="#FF477b36"
        android:type="linear"
        />    
</shape>

ハーフオーバーレイ.xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle"
    >
    <solid
        android:color="#40000000"
        />
</shape>

レイヤーリスト.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list
    xmlns:android="http://schemas.android.com/apk/res/android"
    >
    <item
        android:drawable="@drawable/green_horizontal_gradient"
        android:id="@+id/green_gradient"
        />
    <item
        android:drawable="@drawable/half_overlay"
        android:id="@+id/half_overlay"
        android:top="50dp"
        />
</layer-list>

test.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center"
    >
    <TextView
        android:id="@+id/image_test"
        android:background="@drawable/layer_list"
        android:layout_width="fill_parent"
        android:layout_height="100dp"
        android:layout_marginLeft="15dp"
        android:layout_marginRight="15dp"
        android:gravity="center"
        android:text="Layer List Drawable!"
        android:textColor="@android:color/white"
        android:textStyle="bold"
        android:textSize="26sp"     
        />
</RelativeLayout>

さて、基本的に水平方向の緑のグラデーションは、XMLでシェイプグラデーションを作成しました。次に、半分透明のグレーで長方形のシェイプを作りました。これはレイヤーリストのXMLにインライン化すれば、このような余分なファイルは不要になると思うのですが、どうでしょうか。しかし、レイヤーリストのXMLファイルには、ちょっと面倒な部分があります。私は緑のグラデーションを一番下のレイヤーとして置き、次にハーフオーバーレイを2番目のレイヤーとして置き、上から50dpだけオフセットしました。もちろん、この数値は固定された50dpではなく、常にビューサイズの半分であることが望ましいでしょう。パーセンテージは使えないと思いますが。ここから、TextViewをtest.xmlレイアウトに挿入し、layer_list.xmlファイルを背景として使用しました。高さを100dp(オーバーレイのオフセットの2倍のサイズ)に設定すると、以下のようになりました。

タダ!

もう一つの編集 : つまり、3つの別々のXMLファイルはもう必要ないのです!私は、シェイプをアイテムとしてレイヤーリストに埋め込むことができることに気づきました。つまり、もう3つの別々のXMLファイルは必要ありません!このように組み合わせて、同じ結果を得ることができます。

レイヤーリスト.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list
    xmlns:android="http://schemas.android.com/apk/res/android"
    >
    <item>
        <shape
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:shape="rectangle"
            >
            <corners
                android:radius="3dp"
                />
            <gradient
                android:angle="0"
                android:startColor="#FF63a34a"
                android:endColor="#FF477b36"
                android:type="linear"
                />    
        </shape>
    </item>
    <item
        android:top="50dp" 
        >
        <shape
            android:shape="rectangle"
            >
            <solid
                android:color="#40000000"
                />
        </shape>            
    </item>
</layer-list>

この方法なら、いくらでもアイテムを重ねることができますよ。Javaでもっと汎用性の高い結果が得られるかどうか、遊んでみようかな。

これが最後の編集だと思うのですが・・・。 : なるほど、では次のようにJavaで位置決めをすれば間違いなく直りますね。

    TextView tv = (TextView)findViewById(R.id.image_test);
    LayerDrawable ld = (LayerDrawable)tv.getBackground();
    int topInset = tv.getHeight() / 2 ; //does not work!
    ld.setLayerInset(1, 0, topInset, 0, 0);
    tv.setBackgroundDrawable(ld);

しかし! これは、TextViewが描画された後でないと計測できないという、またまた厄介な問題を引き起こします。でも、手動でtopInsetに数値を入れるとうまくいきます。

嘘です、もう一回編集します

コンテナの高さに合わせてレイヤーの描画を手動で更新する方法を発見しました。 こちら . このコードは、onCreate()メソッドに記述します。

final TextView tv = (TextView)findViewById(R.id.image_test);
        ViewTreeObserver vto = tv.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                LayerDrawable ld = (LayerDrawable)tv.getBackground();
                ld.setLayerInset(1, 0, tv.getHeight() / 2, 0, 0);
            }
        });

そして、私は終わりました! ふぅー! :)