RoundRectShapeの引数について調査した
角丸画像をコードから生成したくて、ShapeDrawableを使うことになった。 角丸にするにはRoundRectShapeをShapeDrawableに食わせるんだけど、その引数がドキュメントだけだと分からなかったので検証した。
RoundRectShape(float outerRadii, RectF inset, float innerRadii) RoundRectShape constructor. Specifies an outer (round)rect and an optional inner (round)rect. outerRadii float: An array of 8 radius values, for the outer roundrect. The first two floats are for the top-left corner (remaining pairs correspond clockwise). For no rounded corners on the outer rectangle, pass null.
時計回りで指定すればいいんだよってことは分かるけど、8つ渡すのはよく分からなかった。
RoundRectShape | Android Developers
結論からいうと以下のようになっている
float[]{
topLeft-x, topLeft-y,
topRight-x, topRight-y,
bottomRight-x, bottomRight-y,
bottomLeft-x, bottomLeft-y
}
4つの角 + xとyで8つある理由ですね。 検証に使用したコードを載せておきます。
import android.content.Context; import android.databinding.DataBindingUtil; import android.graphics.Color; import android.graphics.Paint; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.RoundRectShape; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import jp.tomorrowkey.android.roundrectshapeapp.databinding.ActivityMainBinding; import jp.tomorrowkey.android.roundrectshapeapp.databinding.ListItemBinding; public class MainActivity extends AppCompatActivity { ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.activity_main); binding.recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); binding.recyclerView.setAdapter(new MyRecyclerViewAdapter(this)); } private static class MyRecyclerViewAdapter extends RecyclerView.Adapter<ViewHolder> { private static float[][] PATTERNS = new float[][]{ {50, 50, 0, 0, 0, 0, 0, 0}, {0, 0, 50, 50, 0, 0, 0, 0}, {0, 0, 0, 0, 50, 50, 0, 0}, {0, 0, 0, 0, 0, 0, 50, 50}, {50, 50, 50, 50, 0, 0, 0, 0}, {25, 50, 25, 50, 0, 0, 0, 0}, {25, 50, 25, 50, 25, 50, 25, 50}, }; private static final int SOLID_COLOR = Color.rgb(0x00, 0x80, 0x80); private static final int RECT_SIZE = 100; LayoutInflater inflater; public MyRecyclerViewAdapter(Context context) { inflater = LayoutInflater.from(context); } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new ViewHolder(inflater.inflate(R.layout.list_item, parent, false)); } @Override public void onBindViewHolder(ViewHolder holder, int position) { ListItemBinding binding = DataBindingUtil.bind(holder.itemView); float[] outerR = PATTERNS[position]; binding.textView.setText("{" + Util.join(outerR, " ,") + "}"); ShapeDrawable drawable = new ShapeDrawable(new RoundRectShape(outerR, null, null)); drawable.getPaint().setColor(SOLID_COLOR); drawable.getPaint().setStyle(Paint.Style.FILL); drawable.setIntrinsicHeight(RECT_SIZE); drawable.setIntrinsicWidth(RECT_SIZE); binding.imageView.setImageDrawable(drawable); } @Override public int getItemCount() { return PATTERNS.length; } } private static class ViewHolder extends RecyclerView.ViewHolder { public ViewHolder(View itemView) { super(itemView); } } }
ObjectAnimatorでTextViewの内容を書き換える
ObjectAnimatorおもろいなー setNumber(int)を持つNumberTextViewというクラスを作り
public class NumberTextView extends AppCompatTextView { public NumberTextView(Context context) { super(context); } public NumberTextView(Context context, AttributeSet attrs) { super(context, attrs); } public NumberTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public void setNumber(int number) { setText(String.valueOf(number)); } }
ObjectAnimatorでエイッとすると
ObjectAnimator objectAnimator = ObjectAnimator.ofInt(binding.numberTextView, "number", 0, 10000); objectAnimator.setDuration(10 * 1000); objectAnimator.setInterpolator(new LinearInterpolator()); objectAnimator.start();
数値がテキストとして表示される
単にリフレクション使って値を設定しているだけなのかなー。
ProgressBarの値をアニメーションで変更する
↑こういうことやりたいんだけど、ProgressBar#setProgress(:int)
だとアニメーションしてくれない。
そうだ、ObjectAnimatorを使ってアニメーションを実現しよう。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:context=".activity.MainActivity" > <LinearLayout android:orientation="vertical" android:gravity="center" android:layout_width="match_parent" android:layout_height="match_parent"> <ProgressBar style="?android:attr/progressBarStyleHorizontal" android:id="@+id/progress_bar" android:layout_margin="16dp" android:layout_width="match_parent" android:layout_height="wrap_content"/> <Button android:id="@+id/update_progress_button" android:text="Update progress" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout> </layout>
public class MainActivity extends AppCompatActivity { ActivityMainBinding binding; ObjectAnimator objectAnimator; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.activity_main); binding.updateProgressButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int progress = (int) (Math.random() * 100); setProgressWithAnimation(progress); } }); } private void setProgressWithAnimation(int progress) { int[] values = {binding.progressBar.getProgress(), progress}; if (objectAnimator == null) { objectAnimator = ObjectAnimator.ofInt(binding.progressBar, "progress", values); } else { objectAnimator.cancel(); objectAnimator.setIntValues(values); } objectAnimator.setDuration(1000); objectAnimator.setInterpolator(new DecelerateInterpolator(2.0f)); objectAnimator.start(); } }
Activityに書いちゃうとアレなんでカスタムViewでやるとスッキリしそうでよさそう。
アニメーションしている最中はそのときのProgressの値になってしまうので、getProgress()
を使いたいケースではこれは使えない。
Androidエミュレータにテキストをペーストすることができない時代がありました
Androidエミュレータは母艦とクリップボードの共有がされないので、パスワードなどの複雑なテキストを毎回手打ちしなくてはならなくて面倒でした。
そこで、簡単にコピペできるような関数を作りました。
function paste_in_android() { e=$(pbpaste | sed -e "s/ /\\\\ /g" | sed -e "s/'/\\\\'/g" | sed -e 's/"/\\"/g') adb shell input text "$e" }
クリップボードに入っているテキストをadb経由で入力します。
input text
を使っているためマルチバイト文字を渡すことはできなく、さらにIMEの状態によって入力のされ方が変わります。 adbを経由するので、エミュレータだけではなく実機でも使うことができます。面倒なURLの入力にも使えるんじゃないでしょうか。
うまくうごかなかったら、エスケープすべき文字があるんだと思います。sed
でエスケープ処理を追加してください。
私はターミナルで入力するのすら面倒なのでAlfred workflowにしておきました。(リポジトリのgifアニを見ればどういう風に使うのか一発で分かると思います。)
github.com
べんり〜
という時代もありました。 現代のAndroidエミュレータではいい感じにしてくれるので、EditTextなどを長押しすればペーストすることができます。
テキストボックス長押しとかでコンテキストメニューだしてペーストできない?
— Sosuke Masui ⋈ (@esmasui) August 28, 2019
コマンドラインから特定のクラスのみテストする
次のコマンドで実現できる
./gradlew connectedAndroidTest -Pcom.android.tools.instrumentationTestRunnerArgs=class=com.example.Util
com.android.tools.instrumentationTestRunnerArgs
にはいろんなオプションが指定でき、どんなオプションがあるかは次のURLから見ることができる
AndroidJUnitRunner | Android Developers
複数の引数を指定する場合はカンマで区切る
./gradlew connectedAndroidTest -Pcom.android.tools.instrumentationTestRunnerArgs=class=com.example.Util,size=medium
キーが重複するハッシュをマージする
2つのハッシュをマージするときにキーが重複するとレシーバを優先されてしまう
$ pry [1] pry(main)> h1 = {a: [1, 2, 3], b: [1, 2, 3]} => {:a=>[1, 2, 3], :b=>[1, 2, 3]} [2] pry(main)> h2 = {a: [10], c: [10,11]} => {:a=>[10], :c=>[10, 11]} [3] pry(main)> h1.merge(h2) => {:a=>[10], :b=>[1, 2, 3], :c=>[10, 11]}
Hash#mergeはブロックを受け取ることができて、キーが重複した時の挙動を指定することができる
$ pry [1] pry(main)> h1 = {a: [1, 2, 3], b: [1, 2, 3]} => {:a=>[1, 2, 3], :b=>[1, 2, 3]} [2] pry(main)> h2 = {a: [10], c: [10,11]} => {:a=>[10], :c=>[10, 11]} [3] pry(main)> h1.merge(h2) { |key, h1v, h2v| h1v + h2v } => {:a=>[1, 2, 3, 10], :b=>[1, 2, 3], :c=>[10, 11]}
Array#shuffleで同じ結果を得る
shuffleの引数に同じRandomオブジェクトを渡せば何度シャッフルしても同じ結果になる
$ pry [1] pry(main)> Array(1...10).shuffle(random: Random.new(473)) => [6, 4, 9, 5, 8, 3, 2, 1, 7] [2] pry(main)> Array(1...10).shuffle(random: Random.new(473)) => [6, 4, 9, 5, 8, 3, 2, 1, 7] [3] pry(main)> Array(1...10).shuffle(random: Random.new(473)) => [6, 4, 9, 5, 8, 3, 2, 1, 7]