最近看見一個好庫github.com/eleme/UEToo…linux
自從我把這個工具給咱們設計同窗安利以後,她們就愛的不要不要的,用過安卓系統開發者選項的同窗知道,有一個查看邊界的按鈕,可是有時候呢,以爲功能不夠,由於開發者選項顯示邊距的和android studio的Layout Inspector 或者DDMS 的uiautomator工具android
同樣都是靜態顯示view邊距以及相關狀態,可是!有了UEtool,不只能看見view的各類屬性,你還能動態改!也就是說你不只能看你還能摸,有了這個工具,我不再用爲了改1,2個dp的邊距我再編譯運行了!設計同窗也能更好調整UI了。以UETool官方Demo的捕捉控件功能例吧。git
如何快速分析一個咱們徹底陌生的app呢,那上工具,第一步先看看目前activity是誰adb shell dumpsys window w | findstr mCurrent
或者
adb shell dumpsys window w | grep mCurrent
該命令能區分activity和popupwindow (win用findstr mac/linux用 grep)
好目標就是me.ele.uetool.TransparentActivity
github
第二步,看目標Viewshell
這就是捕捉控件功能對應的特殊ViewGroup了,它有一個成員變量叫AttrsDialog
是一個自定義Dialog,展現的就是View屬性列表的RecyclerView,咱們重點看它的adapterbash
public static class Adapter extends RecyclerView.Adapter {
private List<Item> items = new ItemArrayList<>();
private AttrDialogCallback callback;
public void setAttrDialogCallback(AttrDialogCallback callback) {
this.callback = callback;
}
public void notifyDataSetChanged(Element element) {
items.clear();
for (String attrsProvider : UETool.getInstance().getAttrsProvider()) {
try {
IAttrs attrs = (IAttrs) Class.forName(attrsProvider).newInstance();
items.addAll(attrs.getAttrs(element));
} catch (Exception e) {
e.printStackTrace();
}
}
notifyDataSetChanged();
}
複製代碼
好來一個下一個斷點,咱們跳過複雜的封裝邏輯,直接看運行的函數看調用棧app
這裏咱們看見了數據源其實就是element,這個element存有一個View成員變量public class UETCore implements IAttrs {
@Override
public List<Item> getAttrs(Element element) {
List<Item> items = new ArrayList<>();
View view = element.getView();
items.add(new SwitchItem("Move", element, SwitchItem.Type.TYPE_MOVE));
IAttrs iAttrs = AttrsManager.createAttrs(view);
if (iAttrs != null) {
items.addAll(iAttrs.getAttrs(element));
}
items.add(new TitleItem("COMMON"));
items.add(new TextItem("Class", view.getClass().getName()));
items.add(new TextItem("Id", Util.getResId(view)));
items.add(new TextItem("ResName", Util.getResourceName(view.getResources(), view.getId())));
items.add(new TextItem("Clickable", Boolean.toString(view.isClickable()).toUpperCase()));
items.add(new TextItem("Focused", Boolean.toString(view.isFocused()).toUpperCase()));
items.add(new AddMinusEditItem("Width(dp)", element, EditTextItem.Type.TYPE_WIDTH, px2dip(view.getWidth())));
items.add(new AddMinusEditItem("Height(dp)", element, EditTextItem.Type.TYPE_HEIGHT, px2dip(view.getHeight())));
items.add(new TextItem("Alpha", String.valueOf(view.getAlpha())));
Object background = Util.getBackground(view);
if (background instanceof String) {
items.add(new TextItem("Background", (String) background));
} else if (background instanceof Bitmap) {
items.add(new BitmapItem("Background", (Bitmap) background));
}
items.add(new AddMinusEditItem("PaddingLeft(dp)", element, EditTextItem.Type.TYPE_PADDING_LEFT, px2dip(view.getPaddingLeft())));
items.add(new AddMinusEditItem("PaddingRight(dp)", element, EditTextItem.Type.TYPE_PADDING_RIGHT, px2dip(view.getPaddingRight())));
items.add(new AddMinusEditItem("PaddingTop(dp)", element, EditTextItem.Type.TYPE_PADDING_TOP, px2dip(view.getPaddingTop())));
items.add(new AddMinusEditItem("PaddingBottom(dp)", element, EditTextItem.Type.TYPE_PADDING_BOTTOM, px2dip(view.getPaddingBottom())));
return items;
}
複製代碼
有View對象固然dialog顯示View各個屬性沒問題,那麼很奇怪,這個view是MainActivity的,這個新開的TransparentActivity
是怎麼拿到數據源的呢 莫慌,看這個函數棧,注意到EditAttrLayout
類的triggerActionUp方法的elementide
@Override
public void triggerActionUp(MotionEvent event) {
final Element element = getTargetElement(event.getX(), event.getY());
if (element != null) {
EditAttrLayout.this.element = element;
invalidate();
if (dialog == null) {
dialog = new AttrsDialog(getContext());
dialog.setAttrDialogCallback(new AttrsDialog.AttrDialogCallback() {
@Override
public void enableMove() {
mode = new MoveMode();
dialog.dismiss();
}
});
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
element.reset();
invalidate();
}
});
}
dialog.show(element);
}
}
複製代碼
好發現關鍵句getTargetElement(event.getX(), event.getY());
點進去會跳到父類CollectViewsLayout的getTargetElement方法函數
protected Element getTargetElement(float x, float y) {
Element target = null;
for (int i = elements.size() - 1; i >= 0; i--) {
final Element element = elements.get(i);
if (element.getRect().contains((int) x, (int) y)) {
if (element != childElement) {
childElement = element;
parentElement = element;
} else if (parentElement != null) {
parentElement = parentElement.getParentElement();
}
target = parentElement;
break;
}
}
if (target == null) {
Toast.makeText(getContext(), getResources().getString(R.string.uet_target_element_not_found, x, y), Toast.LENGTH_SHORT).show();
}
return target;
}
複製代碼
element和elements有直接關係再看到elements List<Element> elements
怎麼來的呢?list數據填充無非2種經常使用的要麼add要麼addAll直接command +f
或者ctrl + f
搜索elements.add就發現了數據源設置的函數工具
private void traverse(View view) {
if (UETool.getInstance().getFilterClasses().contains(view.getClass().getName())) return;
if (view.getAlpha() == 0 || view.getVisibility() != View.VISIBLE) return;
if (getResources().getString(R.string.uet_disable).equals(view.getTag())) return;
elements.add(new Element(view));
if (view instanceof ViewGroup) {
ViewGroup parent = (ViewGroup) view;
for (int i = 0; i < parent.getChildCount(); i++) {
traverse(parent.getChildAt(i));
}
}
}
複製代碼
來搞個斷點看調用棧,或者就在element 構造方法下斷點就能省掉上面從element到elemnets的分析 來看下圖
UETool拿到targetActivity也就是MainActivity,而後反射拿到decoreView,而後調用 EditAttrLayout類的父類CollectViewsLayout類traverse方法。
至此按時間順序總結一下,CollectViewsLayout
類的onAttachedToWindow
經過反射拿到目標MainActivity的decoreView
,去給CollectViewsLayout
的成員變量List<Element> elements
add包裝了decoreView
的Element,而後用戶也就是我,點了UETool的操做控件
按鈕,UP事件的時候AttrsDialog
的show方法調用adapter.notifyDataSetChanged(element);
給Adapter設置被UETCore
解開element各類屬性的List<Item>
做爲數據源
好了,原理簡單初探到這裏。餓了麼大神的代碼就這麼大概摸完了,代碼封裝的很不錯,建議有興趣的同窗能夠看看,學習一下。