2020年3月18日水曜日

Kotlin メモ : repeat

repeat

指定回数だけ action を実行します。 val count = supportFragmentManager.backStackEntryCount repeat(count) { supportFragmentManager.popBackStack() }



2020年3月16日月曜日

Drag を実装する その2 : GestureDetector

GestureDetector を使うと onScroll() で移動距離を教えてくれる。ただし、GestureDetector は ACTION_UP や ACTION_CANCEL を通知してくれないのが難点である。 class SimpleDragView : FrameLayout { constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( context, attrs, defStyleAttr ) private val targetView: View private val gestureDetector: GestureDetector init { val size = (100 * resources.displayMetrics.density).toInt() targetView = View(context).apply { layoutParams = LayoutParams(size, size) setBackgroundColor(Color.RED) } addView(targetView) gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() { override fun onScroll( e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float ): Boolean { targetView.translationX -= distanceX targetView.translationY -= distanceY return true } override fun onDown(e: MotionEvent): Boolean { val pointerIndex = e.actionIndex val x = e.getX(pointerIndex) val y = e.getY(pointerIndex) if (!(x.toInt() in 0..width && y.toInt() in 0..height)) { return false } val left = targetView.translationX val right = left + targetView.width val top = targetView.translationY val bottom = top + targetView.height if (!(x in left..right && y in top..bottom)) { return false } return true } }) gestureDetector.setIsLongpressEnabled(false) } override fun onTouchEvent(ev: MotionEvent): Boolean { return gestureDetector.onTouchEvent(ev) || super.onTouchEvent(ev) } }


setIsLongpressEnabled(false) しないと下のように LongPress 判定されたときに onScroll() が呼ばれない。




assets 内のファイルの url

いつも忘れるのでメモっておく

src/main/assets/index.html の url は
file:///android_asset/index.html


2020年3月13日金曜日

Drag を実装する その1 : GestureDetector なし

Drag and scale | Android Developers (MotionEventCompat を使ってたりちょっと古い)を参考に変えたもの class SimpleDragView : FrameLayout { constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( context, attrs, defStyleAttr ) private var activePointerId = INVALID_POINTER_ID private var lastTouchX = 0f private var lastTouchY = 0f private val targetView: View init { val size = (100 * resources.displayMetrics.density).toInt() targetView = View(context).apply { layoutParams = LayoutParams(size, size) setBackgroundColor(Color.RED) } addView(targetView) } override fun onTouchEvent(ev: MotionEvent): Boolean { when (ev.actionMasked) { MotionEvent.ACTION_DOWN -> { val pointerIndex = ev.actionIndex val x = ev.getX(pointerIndex) val y = ev.getY(pointerIndex) if (!(x.toInt() in 0..width && y.toInt() in 0..height)) { return false } val left = targetView.translationX val right = left + targetView.width val top = targetView.translationY val bottom = top + targetView.height if (!(x in left..right && y in top..bottom)) { return false } lastTouchX = x lastTouchY = y activePointerId = ev.getPointerId(0) } MotionEvent.ACTION_MOVE -> { if (activePointerId == INVALID_POINTER_ID) { return false } val pointerIndex = ev.findPointerIndex(activePointerId) val x = ev.getX(pointerIndex) val y = ev.getY(pointerIndex) if (!(x.toInt() in 0..width && y.toInt() in 0..height)) { return false } val diffX = x - lastTouchX val diffY = y - lastTouchY targetView.translationX += diffX targetView.translationY += diffY lastTouchX = x lastTouchY = y } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { activePointerId = INVALID_POINTER_ID } MotionEvent.ACTION_POINTER_UP -> { if (activePointerId == INVALID_POINTER_ID) { return false } val pointerIndex = ev.actionIndex if (ev.getPointerId(pointerIndex) != activePointerId) { return false } val newPointerIndex = if (pointerIndex == 0) 1 else 0 val x = ev.getX(newPointerIndex) val y = ev.getY(newPointerIndex) if (!(x.toInt() in 0..width && y.toInt() in 0..height)) { activePointerId = INVALID_POINTER_ID return false } lastTouchX = x lastTouchY = y activePointerId = ev.getPointerId(newPointerIndex) } } return true } }


2020年3月11日水曜日

dialogCornerRadius でダイアログの角丸具合を指定する

Android Pie(API Level 28)から ?android:attr/dialogCornerRadius でダイアログの角丸具合を指定できるようになりましたが、AppCompat や MaterialComponents では ?attr/dialogCornerRadius としてバックポートされています。 <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="dialogCornerRadius">12dp</item> </style> (<item name="android:dialogCornerRadius">12dp</item> だと Android Pie(API Level 28)以降だけ角丸になります。)



<item name="dialogCornerRadius">12dp</item>

API Level 21



API Level 27



API Level 28





<item name="android:dialogCornerRadius">12dp</item>

API Level 21



API Level 27



API Level 28





2020年3月10日火曜日

Material Design Components for Android 1.1.0 でボタンのデフォルトカラーが colorAccent から colorPrimary に変わった

Theme.AppCompat.Light.DarkActionBar



Theme.MaterialComponents.Light.DarkActionBar (1.0.0)



Theme.MaterialComponents.Light.DarkActionBar (1.1.0)





何もしてないのに(MDC の version を 1.1.0 に上げたけど...) 色が!変わった!

ピンクはどこの色かというと colorAccent に指定している色です。では緑はどこの色かというと colorPrimary に指定している色です。

ボタン系のデフォルトカラーが 1.1.0 から colorPrimary に変わったようです。



AlertDialog のボタンの色は

?attr/materialAlertDialogTheme に指定されている
ThemeOverlay.MaterialComponents.MaterialAlertDialog

Base.ThemeOverlay.MaterialComponents.MaterialAlertDialog

Base.V14.ThemeOverlay.MaterialComponents.MaterialAlertDialog の <item name="buttonBarPositiveButtonStyle">@style/Widget.MaterialComponents.Button.TextButton.Dialog</item> <item name="buttonBarNegativeButtonStyle">@style/Widget.MaterialComponents.Button.TextButton.Dialog</item> <item name="buttonBarNeutralButtonStyle">@style/Widget.MaterialComponents.Button.TextButton.Dialog.Flush</item>
Widget.MaterialComponents.Button.TextButton.Dialog の <item name="android:textColor">@color/mtrl_text_btn_text_color_selector</item>
@color/mtrl_text_btn_text_color_selector <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:alpha="1.00" android:color="?attr/colorPrimary" .../> <item android:alpha="0.60" android:color="?attr/colorOnSurface" .../> <item android:alpha="1.00" android:color="?attr/colorPrimary" .../> <item android:alpha="0.38" android:color="?attr/colorOnSurface"/> </selector> あー、colorPrimary と colorOnSurface になったのねぇ。

ちなみに 1.0.0 のときの @color/mtrl_text_btn_text_color_selector では colorAccent 使ってます。 <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="?attr/colorAccent" android:state_enabled="true"/> <item android:color="@color/mtrl_btn_text_color_disabled"/> </selector>


ボタンの色を変えたいときは 「AlertDialog の Negative ボタンの文字色を変える」 と同じ感じでやればOK <style name="ThemeOverlay.MyApp.MaterialAlertDialog" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog"> <item name="buttonBarPositiveButtonStyle">@style/Widget.MyApp.Button.TextButton.Dialog</item> <item name="buttonBarNegativeButtonStyle">@style/Widget.MyApp.Button.TextButton.Dialog</item> </style> <style name="Widget.MyApp.Button.TextButton.Dialog" parent="Widget.MaterialComponents.Button.TextButton.Dialog"> <item name="android:textColor">#1565C0</item> </style> AlertDialog.Builder(this, R.style.ThemeOverlay_MyApp_MaterialAlertDialog) .setTitle("Title") .setMessage("Message") .setPositiveButton(android.R.string.ok, null) .setNegativeButton(android.R.string.cancel, null) .show()



2020年3月9日月曜日

Material Design Components for Android 1.1.0 から Checkbox で android:button を指定するなら app:useMaterialThemeColors="false" も必要(なことが多い)

Checkbox のマークをカスタマイズするときは android:button に drawable resource を指定します。

例えば以下のような drawable を用意して Checkbox の android:button に指定したのが次のスクリーンショットの下2つです。 <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:bottom="7dp" android:left="7dp" android:right="7dp" android:top="7dp"> <selector> <item android:state_checked="true"> <shape android:shape="oval"> <size android:width="18dp" android:height="18dp" /> <stroke android:width="6dp" android:color="#6666ff" /> </shape> </item> <item> <shape android:shape="oval"> <size android:width="18dp" android:height="18dp" /> <stroke android:width="2dp" android:color="#cccccc" /> </shape> </item> </selector> </item> </layer-list> <CheckBox ... android:button="@drawable/checkbox" ... />



全く同じコードで Activity の theme を Material Design Components for Android 1.0.0 に変えたのが次のスクリーンショットです。



同じようになってますね。

全く同じコードで Activity の theme を Material Design Components for Android 1.1.0 に変えたのが次のスクリーンショットです。



なんということでしょう!android:button で指定した drawable resource が Material Design の theme color で tint されるようになりました。tint で使われる色は colorControlActivated と colorOnSurface です。

この挙動を止めるには app:useMaterialThemeColors="false" を指定します。 <CheckBox ... android:button="@drawable/checkbox" app:useMaterialThemeColors="false" ... />



tint されなくなりました!



2020年3月6日金曜日

Facebook sdk で <meta-data> を設定しているのに初期化されていないと怒られる問題に対応した

いやー、まったくひどい落とし穴でしたよ。

もとの構成はこんな感じです(facebookAppId の値はダミーです)。

build.gradle android { ... defaultConfig { ... manifestPlaceholders = [facebookAppId: "1234567890000000"] } ... } dependencies { ... implementation "com.facebook.android:facebook-android-sdk:6.1.0" implementation "com.facebook.android:facebook-share:6.1.0" } <?xml version="1.0" encoding="utf-8"?> <manifest ...> ... <application ...> ... <meta-data android:name="com.facebook.sdk.ApplicationId" android:value="${facebookAppId}" /> <provider android:name="com.facebook.FacebookContentProvider" android:authorities="com.facebook.app.FacebookContentProvider${facebookAppId}" android:exported="true" /> </application> </manifest>
公式のドキュメント(https://developers.facebook.com/docs/android/getting-started#app_id)には meta-data を AndroidManifest に書けば勝手に初期化処理がされると書いてあるのに、実際に動かすと以下の初期化処理が走っていないというエラーで落ちます。

java.lang.ExceptionInInitializerError
     Caused by: The SDK has not been initialized, make sure to call FacebookSdk.sdkInitialize() first.


調べてみると FacebookSdk の loadDefaultsFromMetadata() の中で meta-data から読みんだ com.facebook.sdk.ApplicationId の値が Float になっていることが判明しました!(facebookAppId が全て数字だからそうなるのでしょうか...)

下記の Object appId が Float になってしまっていたのです。そのためその後の if 文に入らず、applicationId がセットされていませんでした。

FacebookSdk.java // Package private for testing only static void loadDefaultsFromMetadata(Context context) { if (context == null) { return; } ApplicationInfo ai = null; try { ai = context.getPackageManager().getApplicationInfo( context.getPackageName(), PackageManager.GET_META_DATA); } catch (PackageManager.NameNotFoundException e) { return; } if (ai == null || ai.metaData == null) { return; } if (applicationId == null) { Object appId = ai.metaData.get(APPLICATION_ID_PROPERTY); if (appId instanceof String) { String appIdString = (String) appId; if (appIdString.toLowerCase(Locale.ROOT).startsWith("fb")) { applicationId = appIdString.substring(2); } else { applicationId = appIdString; } } else if (appId instanceof Integer) { throw new FacebookException( "App Ids cannot be directly placed in the manifest." + "They must be prefixed by 'fb' or be placed in the string resource file."); } } ... } ここの処理を読むと、appId の最初に fb or FB がついているときはそれを省いた部分を applicationId にしていることがわかります。

そこで以下のように meta-data の value を "fb${facebookAppId}" にしたら怒られなくなりました! <?xml version="1.0" encoding="utf-8"?> <manifest ...> ... <application ...> ... <meta-data android:name="com.facebook.sdk.ApplicationId" android:value="fb${facebookAppId}" /> <provider android:name="com.facebook.FacebookContentProvider" android:authorities="com.facebook.app.FacebookContentProvider${facebookAppId}" android:exported="true" /> </application> </manifest>


ちなみに facebookAppId を string resource で用意した場合 <string name="facebook_app_id">1234567890000000</string> とか android { ... defaultConfig { ... resValue "string", "facebook_app_id", "1234567890000000" } ... } のときは、fb を付けなくても com.facebook.sdk.ApplicationId の値は String として読み込まれます。 <meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id" />