編輯:關於Android編程
最近搞了搞app無縫切換主題功能,github上看了看,流行的還是插件式,好吧。。完了研究下了。
不過今天咱要搞的是MultipleTheme這個框架,也有1000多star了,應該可以吧,於是乎ji動的整到項目裡准備大干一場。。。小弟沉默十分鐘後,納尼?要全部重寫用到的view?!而且沒有兼容RecyclerView,CardView。。。看來不維護了,行吧行吧,既然這樣那就當學習了,順便咱給他兼容一下。於是乎,有了本文~
附上框架地址:https://github.com/dersoncheng/MultipleTheme
具體使用github上作者寫的很清楚,這裡就不多說了。
本文內容:1.此框架技術點。
2.從源碼分析RecyclerView緩存機制。
3.此框架如何兼容RecyclerView和CardView。
1.調用ColorUiUtil類中changeTheme(View view,Theme theme) :通過遞歸方式,從參數view開始遍歷其所有子view,如果遍歷到的view繼承了ColorUiInterface接口,那麼調用其實現的接口方法。
public static void changeTheme(View rootView, Resources.Theme theme) { if (rootView instanceof ColorUiInterface) { ((ColorUiInterface) rootView).setTheme(theme); if (rootView instanceof ViewGroup) { int count = ((ViewGroup) rootView).getChildCount(); for (int i = 0; i < count; i++) { changeTheme(((ViewGroup) rootView).getChildAt(i), theme); } } if (rootView instanceof AbsListView) { try { Field localField = AbsListView.class.getDeclaredField("mRecycler"); localField.setAccessible(true); Method localMethod = Class.forName("android.widget.AbsListView$RecycleBin").getDeclaredMethod("clear"); localMethod.setAccessible(true); localMethod.invoke(localField.get(rootView)); } catch (NoSuchFieldException e1) { e1.printStackTrace(); } catch (ClassNotFoundException e2) { e2.printStackTrace(); } catch (NoSuchMethodException e3) { e3.printStackTrace(); } catch (IllegalAccessException e4) { e4.printStackTrace(); } catch (InvocationTargetException e5) { e5.printStackTrace(); } } } else { if (rootView instanceof ViewGroup) { int count = ((ViewGroup) rootView).getChildCount(); for (int i = 0; i < count; i++) { changeTheme(((ViewGroup) rootView).getChildAt(i), theme); } } if (rootView instanceof AbsListView) { try { Field localField = AbsListView.class.getDeclaredField("mRecycler"); localField.setAccessible(true); Method localMethod = Class.forName("android.widget.AbsListView$RecycleBin").getDeclaredMethod("clear"); localMethod.setAccessible(true); localMethod.invoke(localField.get(rootView)); } catch (NoSuchFieldException e1) { e1.printStackTrace(); } catch (ClassNotFoundException e2) { e2.printStackTrace(); } catch (NoSuchMethodException e3) { e3.printStackTrace(); } catch (IllegalAccessException e4) { e4.printStackTrace(); } catch (InvocationTargetException e5) { e5.printStackTrace(); } } } }
2.實現的接口方法,會根據需要改變的屬性調用ViewAttributeUtil類中的各種方法。
如applyBackgroundDrawable(Context,Theme,attrId):最後這個方法會根據Theme獲取到對應的drawable,調用這個view的setBackGroundDrawable()方法實現設置背景。
public static void applyBackgroundDrawable(ColorUiInterface ci, Resources.Theme theme, int paramInt) { TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt}); Drawable drawable = ta.getDrawable(0); if (null != ci) { (ci.getView()).setBackgroundDrawable(drawable); } ta.recycle(); }
3.上面applyBackgroundDrawable(Context,Theme,attrId)方法中第三個參數attrid,又是從ViewAttributeUtil類中getBackGroundAttribute(AttributeSet)得到的:
public static int getBackgroundAttibute(AttributeSet attr) { return getAttributeValue(attr, android.R.attr.background); } public static int getAttributeValue(AttributeSet attr, int paramInt) { int value = -1; int count = attr.getAttributeCount(); for (int i = 0; i < count; i++) { if (attr.getAttributeNameResource(i) == paramInt) { String str = attr.getAttributeValue(i); if (null != str && str.startsWith("?")) { value = Integer.valueOf(str.substring(1, str.length())).intValue(); return value; } } } return value; }
public static void changeTheme(View rootView, Resources.Theme theme) { if (rootView instanceof ColorUiInterface) { ((ColorUiInterface) rootView).setTheme(theme); if (rootView instanceof ViewGroup) { int count = ((ViewGroup) rootView).getChildCount(); for (int i = 0; i < count; i++) { changeTheme(((ViewGroup) rootView).getChildAt(i), theme); } } if (rootView instanceof AbsListView) { try { Field localField = AbsListView.class.getDeclaredField("mRecycler"); localField.setAccessible(true); Method localMethod = Class.forName("android.widget.AbsListView$RecycleBin").getDeclaredMethod("clear"); localMethod.setAccessible(true); localMethod.invoke(localField.get(rootView)); } catch 。。。省略異常 } } else { if (rootView instanceof ViewGroup) { int count = ((ViewGroup) rootView).getChildCount(); for (int i = 0; i < count; i++) { changeTheme(((ViewGroup) rootView).getChildAt(i), theme); } } if (rootView instanceof AbsListView) { try { Field localField = AbsListView.class.getDeclaredField("mRecycler"); localField.setAccessible(true); Method localMethod = Class.forName("android.widget.AbsListView$RecycleBin").getDeclaredMethod("clear"); localMethod.setAccessible(true); localMethod.invoke(localField.get(rootView)); } catch。。。省略異常 } } }咱們看上面這個重要的方法:由於ListView也是ViewGroup,並有緩存機制,在換膚後會有緩存的item再次被復用時,此項item沒有改變theme。就會出現當前可見item換了主題,滑動列表,之前不可見的有一部分item的theme沒有發生變化。 於是這方法,通過反射調用了緩存管理類內部類——RecyclerBin中clear()方法,清空所有item,讓listview將所有item重新創建一遍。 那根據這個原理,將RecyclerView中緩存做一下清空。下面先介紹一下RecyclerView緩存機制和兼容。
private ArrayList
private ArrayList
private ArrayList
private ViewCacheExtension mViewCacheExtension: 開發者可以控制的ViewHolder緩存。
private RecycledViewPool mRecyclerPool: 提供復用ViewHolder池。
public void bindViewToPosition(View view, int position):將某個View綁定到Adapter的某個位置。
public View getViewForPosition(int position):LayoutManager通過此方法獲取某一項的view。
(1)mChangedScrap查找,若匹配到則返回相應holder
(2)mAttachedScrap查找,若匹配到且holder有效則返回相應holder
(3)mViewCacheExtension查找,若匹配到則返回相應holder
(4)mRecyclerPool中查找,若匹配到則返回相應holder
(5)還是沒有匹配的,那麼adapter.createViewHolder(),創建新的holder
(6)返回holder.itemView
import android.content.res.Resources; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ColorUiUtil { public static void changeTheme(View rootView, Resources.Theme theme) { if (rootView instanceof ColorUiInterface) { ((ColorUiInterface) rootView).setTheme(theme); if (rootView instanceof RecyclerView) { int count = ((RecyclerView) rootView).getChildCount(); for (int i = 0; i < count; i++) { changeTheme(((ViewGroup) rootView).getChildAt(i), theme); } } else if (rootView instanceof ViewGroup) { int count = ((ViewGroup) rootView).getChildCount(); for (int i = 0; i < count; i++) { changeTheme(((ViewGroup) rootView).getChildAt(i), theme); } } if (rootView instanceof AbsListView) { try { Field localField = AbsListView.class.getDeclaredField("mRecycler"); localField.setAccessible(true); Method localMethod = Class.forName("android.widget.AbsListView$RecycleBin").getDeclaredMethod("clear"); localMethod.setAccessible(true); localMethod.invoke(localField.get(rootView)); } catch (NoSuchFieldException e1) { e1.printStackTrace(); } catch (ClassNotFoundException e2) { e2.printStackTrace(); } catch (NoSuchMethodException e3) { e3.printStackTrace(); } catch (IllegalAccessException e4) { e4.printStackTrace(); } catch (InvocationTargetException e5) { e5.printStackTrace(); } } else if (rootView instanceof RecyclerView) { try { Field localField = RecyclerView.class.getDeclaredField("mRecycler"); localField.setAccessible(true); Method localMethod = Class.forName("android.support.v7.widget.RecyclerView$Recycler").getDeclaredMethod("clear"); localMethod.setAccessible(true); localMethod.invoke(localField.get(rootView)); Method localMethod1 = Class.forName("android.support.v7.widget.RecyclerView$Recycler").getDeclaredMethod("clearScrap"); localMethod1.setAccessible(true); localMethod1.invoke(localField.get(rootView)); ((RecyclerView) rootView).getRecycledViewPool().clear(); } catch (NoSuchFieldException e1) { e1.printStackTrace(); } catch (ClassNotFoundException e2) { e2.printStackTrace(); } catch (NoSuchMethodException e3) { e3.printStackTrace(); } catch (IllegalAccessException e4) { e4.printStackTrace(); } catch (InvocationTargetException e5) { e5.printStackTrace(); } } } else { if (rootView instanceof RecyclerView) { int count = ((RecyclerView) rootView).getChildCount(); for (int i = 0; i < count; i++) { changeTheme(((ViewGroup) rootView).getChildAt(i), theme); } } else if (rootView instanceof ViewGroup) { int count = ((ViewGroup) rootView).getChildCount(); for (int i = 0; i < count; i++) { changeTheme(((ViewGroup) rootView).getChildAt(i), theme); } } if (rootView instanceof AbsListView) { try { Field localField = AbsListView.class.getDeclaredField("mRecycler"); localField.setAccessible(true); Method localMethod = Class.forName("android.widget.AbsListView$RecycleBin").getDeclaredMethod("clear"); localMethod.setAccessible(true); localMethod.invoke(localField.get(rootView)); } catch (NoSuchFieldException e1) { e1.printStackTrace(); } catch (ClassNotFoundException e2) { e2.printStackTrace(); } catch (NoSuchMethodException e3) { e3.printStackTrace(); } catch (IllegalAccessException e4) { e4.printStackTrace(); } catch (InvocationTargetException e5) { e5.printStackTrace(); } } else if (rootView instanceof RecyclerView) { try { Field localField = RecyclerView.class.getDeclaredField("mRecycler"); localField.setAccessible(true); Method localMethod = Class.forName("android.support.v7.widget.RecyclerView$Recycler").getDeclaredMethod("clear"); localMethod.setAccessible(true); localMethod.invoke(localField.get(rootView)); Method localMethod1 = Class.forName("android.support.v7.widget.RecyclerView$Recycler").getDeclaredMethod("clearScrap"); localMethod1.setAccessible(true); localMethod1.invoke(localField.get(rootView)); ((RecyclerView) rootView).getRecycledViewPool().clear(); } catch (NoSuchFieldException e1) { e1.printStackTrace(); } catch (ClassNotFoundException e2) { e2.printStackTrace(); } catch (NoSuchMethodException e3) { e3.printStackTrace(); } catch (IllegalAccessException e4) { e4.printStackTrace(); } catch (InvocationTargetException e5) { e5.printStackTrace(); } } } } }
setCardBackgroundColor(Color) xml中:app:cardBackgroundColor="?attr/colorPrimaryDark"因為CardView繼承自FrameLayout,所以也可以用setBackGroundDrawable(),但是測試下,在列表中,會出現更改theme無效的bug。
public static int getCardViewAttribute(AttributeSet attributeSet) { return getAttributeValue(attributeSet, android.support.v7.cardview.R.attr.cardBackgroundColor); }(2)在ViewAttributeUtil類中增加一個方法:
public static void applyCardViewBackgroundColor(ColorUiInterface ci, Resources.Theme theme, int paramInt) { TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt}); int color = ta.getColor(0, Color.GREEN); if (null != ci) { ((CardView)ci.getView()).setCardBackgroundColor(color); } ta.recycle(); }(3)在自定義ColorCardView類中,調用的就是上面兩個方法了。
一,介紹android四大組件之一:BroadcastReceiver 翻譯成中文:廣播接收者。在Android中,Broadcast是一種廣泛運用在應用程序之間傳輸信息
Android FrameWork 層給我們提供了很多界面組件,但是在實際的商業開發中這些組件往往並不能完全滿足我們的需求,這時候我們就需要自定義我們自己的視圖和動畫。
Gradle自定義插件在Gradle中創建自定義插件,Gradle提供了三種方式:在build.gradle腳本中直接使用 在buildSrc中使用 在獨立Module中
今天我們來簡單說一下Android NDK的使用方法。眾所周知,so文件在Android的開發過程中起到了很重要的作用,無論與底層設備打交道還是在Android安全領域。