本文來自http://blog.csdn.net/liuxian13183/ ,引用必須註明出處!
2011年開始做安卓開發,今年是第9個年頭。回首這些年,從開發做起,做過組長,也做過架構,主要時間還是在做開發,也就是跟業務打交道。第5個年頭,沒能再上一個臺階,受2016年的“互聯網企業倒閉潮流”的影響,先後在2家公司待了不長時間,來到現在的公司,時間緊-一週一個版本、負擔重-幾年的代碼積累、人員新-小組司齡不超過半年、業務多-1(原業務)+4(新業務)模塊開發,過去的1年多是在趕進度,往往是在持續做業務,主要是直播,很少有機會去整理框架或者Sdk,只能做到核心Api封裝,遇到不少未曾重視過的UI細節。今天來講一講常用的ScrollView+LinearLayout和PopupWindow問題,用於記錄,以防後面再發生這種小問題。
1、爲什麼ViewGroup的addView方法和PopupWindow的show方法都不支持設置width和height?
通常情況下,遇到item不多且會變動的功能,往往考慮使用ScrollView+LinearLayout的方案,ListView之類的控件過於龐大且複用性不強,LinearLayout的Orientation有橫向和縱向兩個添加子View的方式,比較適用這種場景。以往都是針對子View設置padding來保障LinearLayout的寬度和高度;這次想通過設置子View的寬高,讓子View進行自適應,可是實驗失敗:
實驗方案:linearLayout.addView(view),未使用LayoutParams。
翻看源碼:
public void addView(View child, int index) {
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = generateDefaultLayoutParams();
if (params == null) {
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
}
}
addView(child, index, params);
}
確認如果不添加LayoutParams,則默認生成一個(通過LayoutInflate生成的View無LayoutParams)
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
可以得知width和height設置失效。但padding設置有效,因爲它是從AttributSet時取的值。
因此:方案是設置LinearLayout和子View的padding即可。
接下來是PopupWindow的寬高設置問題,同理,見源碼
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
if (isShowing() || !hasContentView()) {
return;
}
TransitionManager.endTransitions(mDecorView);
attachToAnchor(anchor, xoff, yoff, gravity);
mIsShowing = true;
mIsDropdown = true;
final WindowManager.LayoutParams p =
createPopupLayoutParams(anchor.getApplicationWindowToken());
preparePopup(p);
final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
p.width, p.height, gravity, mAllowScrollingAnchorParent);
updateAboveAnchor(aboveAnchor);
p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1;
invokePopup(p);
}
protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
// These gravity settings put the view at the top left corner of the
// screen. The view is then positioned to the appropriate location by
// setting the x and y offsets to match the anchor's bottom-left
// corner.
p.gravity = computeGravity();
p.flags = computeFlags(p.flags);
p.type = mWindowLayoutType;
p.token = token;
p.softInputMode = mSoftInputMode;
p.windowAnimations = computeAnimationResource();
if (mBackground != null) {
p.format = mBackground.getOpacity();
} else {
p.format = PixelFormat.TRANSLUCENT;
}
if (mHeightMode < 0) {
p.height = mLastHeight = mHeightMode;
} else {
p.height = mLastHeight = mHeight;
}
if (mWidthMode < 0) {
p.width = mLastWidth = mWidthMode;
} else {
p.width = mLastWidth = mWidth;
}
p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH
| PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
// Used for debugging.
p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
return p;
}
又是LayoutParams的設置問題。
解決方案:給整體佈局設置寬高以及padding之後,再嵌套一個佈局,即可預防LayoutParams被重置的問題。
2、如果你的彈窗,可能是異步彈出(無論是動畫,還是接口的原因)?會報Can not perform this action after onSaveInstanceState
那你最好放棄commit,換用commitInternal
原因:BackStackRecord
public int commit() {
return commitInternal(false);
}
public int commitAllowingStateLoss() {
return commitInternal(true);
}
int commitInternal(boolean allowStateLoss) {
if (mCommitted) {
throw new IllegalStateException("commit already called");
}
。。。
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
。。。
}
private void checkStateLoss() {
if (mStateSaved) {
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
if (mNoTransactionsBecause != null) {
throw new IllegalStateException(
"Can not perform this action inside of " + mNoTransactionsBecause);
}
}