デザむンのテヌマ。 ブラックゞャックずりィヌクリファレンスを䜿甚

か぀お、私の前に、タスクはAndroidのアプリケヌションのテヌマを䜜成するこずでした。 私が手に入れたかったもの



  1. 倖芳を切り替える機胜-いく぀かの色ずグラフィックを倉曎する
  2. 倉曎はオンザフラむで行う必芁がありたす。ナヌザヌはデザむンのみを倉曎し、他のすべお入力フィヌルドの内容、リスト内の芁玠の䜍眮などは倉曎しないでください。
  3. 将来的には、たずえば時間垯など、ナヌザヌの参加なしにトピックを倉曎できるようにしたい
  4. 既存のコヌドやマヌクアップを倧幅に倉曎したくありたせん。 理想的には、䜕らかの方法でマヌクアップの芁玠をマヌクしたいだけです
  5. アプリケヌションを曎新せずに新しいトピックをアップロヌドできるず䟿利です。




䜕が達成され、どのように実装されたかに぀いお-カットの䞋で。







Stack OverflowずAndroidのドキュメントが提䟛しおいる最も明癜な方法は、Context.setThemeです。 ワンキャッチ-すべおのビュヌを䜜成する前にテヌマをむンストヌルする必芁がありたす。 トピックをその堎で切り替えるこずができないこずはすぐにわかりたす。ナヌザヌはアクティビティ党䜓の完党な再䜜成に間違いなく気付くでしょう。 はい。各アクティビティのコヌドを䜕らかの方法で修正する必芁がありたす。 むンタヌネット䞊で他の掚奚事項は芋぀かりたせんでした情報があれば、リンクに感謝したす。



さお、実装を曞きたしょう。 ブラックゞャックずWeakReferenceを䜿甚。



ポむント4から進めたす。私は自分がコヌドを曞きたくない開発者であるず考えおいたす。 私はコヌドを曞くのが奜きではないので、将来それを曞かないために、たくさんのコヌドを曞く準備ができおいたす。 私は自分自身を助けるこずはできたせん。次のりィンドりが動的なデザむン倉曎の圱響を受けやすいように衚瀺されるずき、盞互䜜甚ロゞックを考えたくありたせん。 芁玠の暪のマヌクアップで、たずえば、その色が背景色ず等しくなるこずを瀺したいだけです。



tagプロパティはこれに圹立ちたす。 たずえば、ListViewでアダプタヌを最適化するためのHolder'ovずしお、アプリケヌションタグのどこかで䜿甚されおいる堎合は問題ありたせん。 コヌドでは、setTag / getTagをidパラメヌタヌず共に䜿甚できたす。 そのような堎所がたくさんある堎合は、怜玢ず眮換が圹立ちたす。



次に、簡単なタグの圢匏を考えたす。 たず、穀物をもみ殻から分離し、このタグが実際にテヌマの䜿甚を瀺しおいるこずを簡単に確認したす。タグは垞に「」蚘号で始たりたす。 これには、リ゜ヌスの名前が続きたす「background」など。 次に、セパレヌタ、「|」などのリ゜ヌス、およびテキストの色、背景画像、背景色などのリ゜ヌスのタむプ。 たずえば、「Chat | tiled_bg」ずいうタむリングを䜿甚したチャットりィンドりの背景。 たぶん、芋た目の矎しさではないかもしれたせんが、すぐに解析したす。 このようなタグを蚘述するずきにミスを最小限に抑えるには、それらを文字列リ゜ヌスに入れお再利甚するこずをお勧めしたす-アプリケヌションではリ゜ヌスを䜿甚したすPrimary | text_fgは77回䜿甚されたす。



最も難しい郚分は背埌にあり、䜕らかの方法でこれらのタグを凊理するだけです。このようなタグを持぀芁玠は、「膚匵」ビュヌ䞭にすぐに凊理し、その埌テヌマを倉曎するたびに凊理する必芁がありたす。 「膚匵」は、アクティビティのsetContentViewずLayoutInflaterの䜿甚の2぀の方法で発生したす。 setContentViewから始めたしょう。



このアプリケヌションでは、すべおのアクティビティは少数の基本アクティビティから継承されたす。 setContentViewメ゜ッドをオヌバヌラむドするだけです

public void setContentView(int id) { super.setContentView(id); HotTheme.manage(mActivity.getWindow().getDecorView()); }
      
      





getDecorViewメ゜ッドは、View階局の「ルヌト」を返したす。

LayoutInflaterを䜿甚しおビュヌを䜜成するずきに同じこずを行うには、ラップしたす。

 public class HotLayoutInflater { private LayoutInflater inflater; private HotLayoutInflater(LayoutInflater inflater) { this.inflater = inflater; } public View inflate(int resource, ViewGroup root, boolean attachToRoot) { View v = inflater.inflate(resource, root, attachToRoot); HotTheme.manage(v); return v; } public View inflate(int resource, ViewGroup root) { View v = inflater.inflate(resource, root); HotTheme.manage(v); return v; } public static HotLayoutInflater wrap(LayoutInflater layoutInflater) { return new HotLayoutInflater(layoutInflater); } public static HotLayoutInflater from(Context context) { return new HotLayoutInflater(LayoutInflater.from(context)); } }
      
      





今-ビュヌ階局の解析



HotTheme.java

  public static void manage(View... views) { for (View v : views) { simpleManage(v); if (v instanceof ViewGroup) { ViewGroup vg = (ViewGroup) v; for (int i = 0; i < vg.getChildCount(); i++) { manage(vg.getChildAt(i)); } } } } public static void simpleManage(View view) { Object t = view.getTag(); if (t instanceof String) { String tag = (String) t; if (tag.startsWith("!")) { tag = tag.substring(1); String[] elements = tag.split("\\|"); String base = elements[0]; for (int i = elements.length - 1; i >= 1; i--) { ThemedView tv = createThemedView(view, base, elements[i]); tv.notifyChange(); HotTheme.sViews.add(tv); } } } }
      
      





ご芧のずおり、タグを含むビュヌは階局から匕き出されおいたす。 念のため、耇数のセパレヌタ「|」が存圚する可胜性があるず考えおいたす。その堎合、リ゜ヌスは各タむプに適甚されたすこれは有甚であるこずが刀明する可胜性がありたす。



さらに、これらの芁玠は特定のThemedViewに倉わり、すべおの魔法を担圓したす。 notifyChangeメ゜ッドは、珟圚のテヌマをこのビュヌに適甚したす。 トピックの倉曎を通知するために、将来のためにThemedViewを保存しおください。耇雑なこずは䜕もありたせん。

ThemedViewクラス自䜓は、Viewの単玔なラッパヌであり、コンテキストリヌクを防ぎたす。

 private static abstract class ThemedView { private WeakReference<View> view; ThemedView(View v) { view = new WeakReference<View>(v); } boolean notifyChange() { View v = view.get(); if (v == null) { return false; } onChange(v); return true; } abstract void onChange(View v); @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ThemedView view1 = (ThemedView) o; View v1 = view.get(); View v2 = view1.view.get(); return (v1 != null ? v1.equals(v2) : v2 == null); } @Override public int hashCode() { if (view == null) { return 0; } View v = view.get(); return v != null ? v.hashCode() : 0; } }
      
      





ここで、テヌマを倉曎するずきに、関心のあるすべおのビュヌに適甚するには、次を呌び出したす。

  for (Iterator<ThemedView> it = views.iterator(); it.hasNext(); ) { if (!it.next().notifyChange()) { it.remove(); } }
      
      





わずかな䜙談
Javaが倧奜きです。 はい、JavaはCより少し遅いです。はい、Pythonよりも柔軟性が䜎いです。 しかし、圌女はい、私にずっおJavaは「圌女」は驚くべきこずをするこずができたす。

  1. 私のために。 真剣に、私はちょうど私が欲しいものを圌女に䌝えおいる。 ビヌルが欲しいなら、圌女はボトルを取りに行き、それを開けお、私が玠晎らしい飲み物を楜しむたで芪切に埅ち、その埌圌女はボトルを捚おたす。 ありがずうgc
  2. 私のために考えたす。 型の匱い蚀語のように、デヌタ型を芚えおおく必芁はありたせん。 メモリがスタックに割り圓おられたのか、ヒヌプに割り圓おられたのかを考える必芁はありたせん。 Javaを䜿甚しおいる堎合、ほずんど考える必芁はほずんどありたせん。倚くの堎合、あなたが望むこずを圌女に説明するだけで十分です。
  3. コヌドを曞いおください。 Javaassist、CGLib、java.lang.reflect.Proxy、JSR-269、泚釈、リフレクション... Javaによるメタプログラミングは玠晎らしいです
  4. キャストしおください。 そしおそれは安党です たあ、ほずんど。 少なくずも@SuppressWarning「未チェック、rawtypes」で圌女に怒鳎るたでは。 ゞェネリックに感謝
  5. Javaは誇りに思っおいたせん。 圌女は、それが圌女の性質に反しおいるずいう事実にもかかわらず、安党でない方法を知っおいたす。


はい、圌女には欠陥がありたす。 圌女はチャットが倧奜きです-Javaよりも冗長な蚀語を芋たこずはありたせんもちろん、蚀語のパスカルは考慮しおいたせん。 しかし、通垞IDEでは、さたざたな自動眮換ずテンプレヌトを䜿甚しおこれを克服できたす。



AndroidはJavaを䜿甚したす。 はい、それは圌女の矎埳の1぀が圌に残っおいなかっただけです。 圌は、矎しく埓順な女性ずいうよりも、酔っおいないひげを剃っおいない男性のように芋えたす。 私は圌に蚀いたす-私はビヌルが欲しい、そしお圌は私に蚀った-定数を取埗し、むンテントを䜜成し、デヌタをシリアル化し、アクティビティを開き、結果を取埗したす...すべおがうたくいけば、それを逆シリアル化し、「ビヌル」タむプに倉換したす。 そしお、はい、ビヌルを埗るためのあなたの操䜜がい぀でも䞭断されるかもしれないこずを心に留めおおいおください。 すでに支払ったずしおも。 1぀の物理プロセスのコンテキストにいる堎合は特に喜ばれたす。







どのタむプのMessage.objに導くかは、Message.whatに䟝存するこずを垞に念頭に眮いおおく必芁がありたす。 そしお、巚倧な切り替えを行いたす。 ずおも快適です。



Androidのコヌド生成は別のこずです。 Javaassit / CGLibに぀いおはほずんど忘れるこずができたす䌌たような実装がいく぀かありたすが、その速床には倚くの芁望がありたす。 残りプロキシ、JSR-269、アノテヌション、リフレクションに぀いおは定期的に眪を犯したすが、倚かれ少なかれ蚱容できる速床で動䜜させるために倚くのゞェスチャヌをしなければなりたせん。



Androidは誇りに思っおいたす。 圌は安党でないこずを知っおいたす。 そしお、これはその性質に反するものではありたせんNDK、RenderScriptなどを考慮に入れる。 はい、リフレクションを介しおのみ䜿甚できたす。これにより、Unsafeの利点のほずんどが倱われたす。



だから、私は䜕をしおいたす。 Javaの服埓のおかげで、WeakReferenceなどのツヌルは非垞にたれにしか䜿甚されず、最も倧胆な゚ロティックな空想でのみ䜿甚されたすたずえば、さたざたなORMでのデヌタ敎合性のサポヌト。 Androidでは、ロマンスの代わりに、WeakReferenceを䜿甚しおBDSMスタむルを支配する必芁がありたす。 オブゞェクトは、未知のラむフサむクルの圱響を受けお、オブゞェクト自身の生掻を送るずいう事実に我慢する必芁がありたす。 コンテキストリヌクコンテキストが発生しないように、WeakReferenceを䜿甚しおそれらに「固執」する必芁がありたす。 おそらく、Androidの䞋で「ベンド」する䟡倀があり、各アクティビティで「終了」するずき、View階局の登録を解陀したすが、問題はそれが倉曎され、ビュヌがなくなるこずです特にListViewの兞型的な芁玠であり、その芁玠は垞に衚瀺/非衚瀺になりたす画面から。 これは䞻に、アプリケヌションモゞュヌルの䞀郚が芖芚郚分に圱響を䞎える堎合にほずんど垞にWeakReferenceを䜿甚する理由です。すべおのビュヌはWeakReferenceのみに保存されるため、䜜業のロゞックが非垞に耇雑になりたす。



ThemedViewに戻りたしょう。その子孫で、onChangeメ゜ッドで、Viewで䜕が起こるかを正確に決定したす。

  private static ThemedView createThemedView(View v, final String base, String element) { ThemeType type = types.get(element); switch (type) { case TILED_BG: return new ThemedView(v) { @Override public void onChange(View v) { Bitmap bmp = decodeBitmap(base + "_bg"); BitmapDrawable bd = new BitmapDrawable(app().getResources(), bmp); bd.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); v.setBackgroundDrawable(bd); } }; case VIEW_COLOR_BG: return new ThemedView(v) { @Override public void onChange(View v) { int color = getColor(base + "_bg"); v.setBackgroundColor(color); if (v instanceof ListView) { // There is an android bug in setCacheColorHint // That caused the IndexOutOfBoundsException // look here: // http://code.google.com/p/android/issues/detail?id=12840 // // Moreover, that bug doesn't allow us to setDrawableCacheColor // for recycled views. That's why we need to perform cleaning up // via reflections // // Fixed in android 4.1.1_r1 try { ((ListView) v).setCacheColorHint(color); } catch (IndexOutOfBoundsException ex) { try { Field mRecycler = AbsListView.class.getDeclaredField("mRecycler"); mRecycler.setAccessible(true); Object recycler = mRecycler.get(v); Method m = recycler.getClass().getDeclaredMethod("clear"); m.setAccessible(true); m.invoke(recycler); } catch (Throwable t) { // No need to report this } } } } }; case VIEW_IMAGE_BG: return new ThemedView(v) { @Override public void onChange(View v) { v.setBackgroundDrawable(decodeDrawable(base + "_bg")); } }; case IMAGE_FG: return new ThemedView(v) { @Override public void onChange(View v) { ((ImageView) v).setImageDrawable(decodeDrawable(base + "_bg")); } }; case TEXT_COLOR: return new ThemedView(v) { @Override public void onChange(View v) { final int color = getColor(base + "_fg"); if (v instanceof TextView) { ((TextView) v).setTextColor(color); } } }; case TEXT_HINT: return new ThemedView(v) { @Override public void onChange(View v) { final int color = getColor(base + "_hint_fg"); if (v instanceof TextView) { ((TextView) v).setHintTextColor(color); } } }; case PAGER: return new ThemedView(v) { @Override public void onChange(View v) { int active = getColor(base + "_active_fg"); int inactive = getColor(base + "_inactive_fg"); int footer = getColor(base + "_footer_bg"); TitlePageIndicator pager = (TitlePageIndicator) v; pager.setSelectedColor(active); pager.setTextColor(inactive); pager.setFooterColor(footer); } }; case DIVIDER: return new ThemedView(v) { @Override public void onChange(View v) { int color = getColor(base + "_divider"); ListView lv = (ListView) v; int h = lv.getDividerHeight(); lv.setDivider(new ColorDrawable(color)); lv.setDividerHeight(h); } }; case TABBUTTON_BG: return new ThemedView(v) { @Override void onChange(View v) { StateListDrawable stateDrawable = new StateListDrawable(); Drawable selectedBd = decodeDrawable(base + "_selected"); stateDrawable.addState(new int[]{android.R.attr.state_selected}, selectedBd); stateDrawable.addState(new int[]{android.R.attr.state_pressed}, selectedBd); stateDrawable.addState(new int[]{}, decodeDrawable(base + "_unselected")); v.setBackgroundDrawable(stateDrawable); } }; case EDITTEXT_COLOR: return new ThemedView(v) { @Override void onChange(View v) { int color = getColor(base + "_fg"); EditText edit = (EditText) v; edit.setTextColor(color); int hintColor = getColor(base + "_disabled_fg"); edit.setHintTextColor(hintColor); } }; case GROUP_TINT: return new ThemedView(v) { @Override void onChange(View v) { int tintColor = getColor(base + "_fg"); ImageView imageView = (ImageView) v; imageView.setColorFilter(tintColor, PorterDuff.Mode.SRC_ATOP); } }; default: throw new IllegalArgumentException("Error in layout: no such type \"" + element + "\" (" + base + ")"); } }
      
      





types.get芁玠コヌドは、単に小文字の文字列でenumを返したす。



おもしろいメ゜ッドdecodeBitmap、decodeDrawable、およびgetColorは残りたした。



  private static ResourceInfo findResource(String base, ResourceType type) { return sCurrentProvider.findResource(base, type); } public static Drawable decodeDrawable(String base) { ResourceInfo info = findResource(base, ResourceType.Drawable); return info.getResources().getDrawable(info.getResId()); } public static Bitmap decodeBitmap(String base) { ResourceInfo info = findResource(base, ResourceType.Drawable); return BitmapFactory.decodeResource(info.getResources(), info.getResId(), Util.newPurgeableBitmapOptions()); } public static int getColor(String base) { ResourceInfo info = findResource(base, ResourceType.Color); return info.getResources().getColor(info.getResId()); }
      
      





ThemeProviderクラスのオブゞェクトはsCurrentProviderずしお機胜したす。sCurrentProviderの唯䞀のタスクは、名前ずタむプによっおリ゜ヌス情報を取埗するこずです。

最も単玔な実装では、いく぀かのトピックIDをリ゜ヌス名のプレフィックスずしお远加したす。

  @Override public ResourceInfo findResource(String name, ResourceType type) { int id = IdUtils.getResId(app().getResources(), mPrefix + "_" + name, type.getType(), PACKAGE_NAME); if (id == 0 && mNext != null) { return mNext.findResource(name, type); } return new ResourceInfo(app().getResources(), id); }
      
      





getResIdメ゜ッドは、Resources.getIdentifierメ゜ッドの小さなラッパヌです。

mNextフィヌルドもThemeProviderオブゞェクトです。 リ゜ヌスが芋぀からなかった堎合にチェヌンを怜玢するために必芁です最終的にはデフォルトが䜿甚されたす。



その結果、次のトピックを䜜成するには、必芁なリ゜ヌスのセットを远加し、䜕らかの皮類のプレフィックスを远加するだけです。 たずえば、チャットりィンドりの背景のリ゜ヌス名

def_chat_bg

night_chat_bg

pink_chat_bg

wood_chat_bg



合蚈



冒頭で述べたように、アプリケヌション自䜓からだけでなく、倖郚からもリ゜ヌスをロヌドできるこずは玠晎らしいこずです。 ゜ヌスは、たずえば別のアプリケヌションです。 この堎合、パッケヌゞ名ずResourcesオブゞェクトを陀き、すべおが同じように機胜したす。 次に、PackageManager.getResourcesForApplicationから取埗できたす。



達成したいこずをもう䞀床思い出しおください。

  1. デザむンを切り替える機胜-完了
  2. 倉曎は「オンザフラむ」で実行する必芁がありたす-これで完了です
  3. 将来的には、ナヌザヌの参加なしにトピックを倉曎したいず考えおいたす-芖点が芋え、障害はありたせん
  4. 既存のコヌドやマヌクアップを倧幅に倉曎したくありたせん。実際、コヌドは倧幅に倉曎されおいたすが、䞻に怜玢ず眮換を行うため、ここにプラス蚘号を远加できたす。
  5. アプリケヌションを曎新せずに新しいトピックをアップロヌド-準備完了




すべおがうたくいったようです。 最埌たで蚘事をマスタヌした皆さんに感謝したす。 誰かが説明されたテクニックを䜿甚しお、アプリケヌションがナヌザヌの気分に適応する胜力を䞎えるこずを願っおいたす-信じおください、圌らはあなたに感謝するでしょう



PSさお、そしお、予想通り、1分間の広告- ここにテヌマを持぀゚ヌゞェントのアプリケヌション。



All Articles