实例介绍
【实例简介】eclipse下使用Android百分比布局
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zhy.android.percent.support;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.v4.view.MarginLayoutParamsCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.TextView;
import com.example.testpercentextends.R;
/**
* Helper for layouts that want to support percentage based dimensions.
* <p/>
* <p>This class collects utility methods that are involved in extracting percentage based dimension
* attributes and applying them to ViewGroup's children. If you would like to implement a layout
* that supports percentage based dimensions, you need to take several steps:
* <p/>
* <ol>
* <li> You need a {@link ViewGroup.LayoutParams} subclass in your ViewGroup that implements
* {@link com.zhy.android.percent.support.PercentLayoutHelper.PercentLayoutParams}.
* <li> In your {@code LayoutParams(Context c, AttributeSet attrs)} constructor create an instance
* of {@link PercentLayoutHelper.PercentLayoutInfo} by calling
* {@link PercentLayoutHelper#getPercentLayoutInfo(Context, AttributeSet)}. Return this
* object from {@code public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo()}
* method that you implemented for {@link com.zhy.android.percent.support.PercentLayoutHelper.PercentLayoutParams} interface.
* <li> Override
* {@link ViewGroup.LayoutParams#setBaseAttributes(TypedArray, int, int)}
* with a single line implementation {@code PercentLayoutHelper.fetchWidthAndHeight(this, a,
* widthAttr, heightAttr);}
* <li> In your ViewGroup override {@link ViewGroup#generateLayoutParams(AttributeSet)} to return
* your LayoutParams.
* <li> In your {@link ViewGroup#onMeasure(int, int)} override, you need to implement following
* pattern:
* <pre class="prettyprint">
* protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
* mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);
* super.onMeasure(widthMeasureSpec, heightMeasureSpec);
* if (mHelper.handleMeasuredStateTooSmall()) {
* super.onMeasure(widthMeasureSpec, heightMeasureSpec);
* }
* }
* </pre>
* <li>In your {@link ViewGroup#onLayout(boolean, int, int, int, int)} override, you need to
* implement following pattern:
* <pre class="prettyprint">
* protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
* super.onLayout(changed, left, top, right, bottom);
* mHelper.restoreOriginalParams();
* }
* </pre>
* </ol>
*/
public class PercentLayoutHelper
{
private static final String TAG = "PercentLayout";
private final ViewGroup mHost;
private static int mWidthScreen;
private static int mHeightScreen;
public PercentLayoutHelper(ViewGroup host)
{
mHost = host;
getScreenSize();
}
private void getScreenSize()
{
WindowManager wm = (WindowManager) mHost.getContext().getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
mWidthScreen = outMetrics.widthPixels;
mHeightScreen = outMetrics.heightPixels;
}
/**
* Helper method to be called from {@link ViewGroup.LayoutParams#setBaseAttributes} override
* that reads layout_width and layout_height attribute values without throwing an exception if
* they aren't present.
*/
public static void fetchWidthAndHeight(ViewGroup.LayoutParams params, TypedArray array,
int widthAttr, int heightAttr)
{
params.width = array.getLayoutDimension(widthAttr, 0);
params.height = array.getLayoutDimension(heightAttr, 0);
}
/**
* Iterates over children and changes their width and height to one calculated from percentage
* values.
*
* @param widthMeasureSpec Width MeasureSpec of the parent ViewGroup.
* @param heightMeasureSpec Height MeasureSpec of the parent ViewGroup.
*/
public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec)
{
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "adjustChildren: " mHost " widthMeasureSpec: "
View.MeasureSpec.toString(widthMeasureSpec) " heightMeasureSpec: "
View.MeasureSpec.toString(heightMeasureSpec));
}
int widthHint = View.MeasureSpec.getSize(widthMeasureSpec);
int heightHint = View.MeasureSpec.getSize(heightMeasureSpec);
if (Log.isLoggable(TAG, Log.DEBUG))
Log.d(TAG, "widthHint = " widthHint " , heightHint = " heightHint);
for (int i = 0, N = mHost.getChildCount(); i < N; i )
{
View view = mHost.getChildAt(i);
ViewGroup.LayoutParams params = view.getLayoutParams();
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "should adjust " view " " params);
}
if (params instanceof PercentLayoutParams)
{
PercentLayoutInfo info =
((PercentLayoutParams) params).getPercentLayoutInfo();
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "using " info);
}
if (info != null)
{
supportTextSize(widthHint, heightHint, view, info);
supportPadding(widthHint, heightHint, view, info);
supportMinOrMaxDimesion(widthHint, heightHint, view, info);
if (params instanceof ViewGroup.MarginLayoutParams)
{
info.fillMarginLayoutParams((ViewGroup.MarginLayoutParams) params,
widthHint, heightHint);
} else
{
info.fillLayoutParams(params, widthHint, heightHint);
}
}
}
}
}
private void supportPadding(int widthHint, int heightHint, View view, PercentLayoutInfo info)
{
int left = view.getPaddingLeft(), right = view.getPaddingRight(), top = view.getPaddingTop(), bottom = view.getPaddingBottom();
PercentLayoutInfo.PercentVal percentVal = info.paddingLeftPercent;
if (percentVal != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, percentVal.basemode);
left = (int) (base * percentVal.percent);
}
percentVal = info.paddingRightPercent;
if (percentVal != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, percentVal.basemode);
right = (int) (base * percentVal.percent);
}
percentVal = info.paddingTopPercent;
if (percentVal != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, percentVal.basemode);
top = (int) (base * percentVal.percent);
}
percentVal = info.paddingBottomPercent;
if (percentVal != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, percentVal.basemode);
bottom = (int) (base * percentVal.percent);
}
view.setPadding(left, top, right, bottom);
}
private void supportMinOrMaxDimesion(int widthHint, int heightHint, View view, PercentLayoutInfo info)
{
try
{
Class clazz = view.getClass();
invokeMethod("setMaxWidth", widthHint, heightHint, view, clazz, info.maxWidthPercent);
invokeMethod("setMaxHeight", widthHint, heightHint, view, clazz, info.maxHeightPercent);
invokeMethod("setMinWidth", widthHint, heightHint, view, clazz, info.minWidthPercent);
invokeMethod("setMinHeight", widthHint, heightHint, view, clazz, info.minHeightPercent);
} catch (NoSuchMethodException e)
{
e.printStackTrace();
} catch (InvocationTargetException e)
{
e.printStackTrace();
} catch (IllegalAccessException e)
{
e.printStackTrace();
}
}
private void invokeMethod(String methodName, int widthHint, int heightHint, View view, Class clazz, PercentLayoutInfo.PercentVal percentVal) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
{
if (Log.isLoggable(TAG, Log.DEBUG))
Log.d(TAG, methodName " ==> " percentVal);
if (percentVal != null)
{
Method setMaxWidthMethod = clazz.getMethod(methodName, int.class);
setMaxWidthMethod.setAccessible(true);
int base = getBaseByModeAndVal(widthHint, heightHint, percentVal.basemode);
setMaxWidthMethod.invoke(view, (int) (base * percentVal.percent));
}
}
private void supportTextSize(int widthHint, int heightHint, View view, PercentLayoutInfo info)
{
//textsize percent support
PercentLayoutInfo.PercentVal textSizePercent = info.textSizePercent;
if (textSizePercent == null) return;
int base = getBaseByModeAndVal(widthHint, heightHint, textSizePercent.basemode);
float textSize = (int) (base * textSizePercent.percent);
//Button 鍜� EditText 鏄疶extView鐨勫瓙绫�
if (view instanceof TextView)
{
((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}
}
private static int getBaseByModeAndVal(int widthHint, int heightHint, PercentLayoutInfo.BASEMODE basemode)
{
switch (basemode)
{
case BASE_HEIGHT:
return heightHint;
case BASE_WIDTH:
return widthHint;
case BASE_SCREEN_WIDTH:
return mWidthScreen;
case BASE_SCREEN_HEIGHT:
return mHeightScreen;
}
return 0;
}
/**
* Constructs a PercentLayoutInfo from attributes associated with a View. Call this method from
* {@code LayoutParams(Context c, AttributeSet attrs)} constructor.
*/
public static PercentLayoutInfo getPercentLayoutInfo(Context context,
AttributeSet attrs)
{
PercentLayoutInfo info = null;
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PercentLayout_Layout);
info = setWidthAndHeightVal(array, info);
info = setMarginRelatedVal(array, info);
info = setTextSizeSupportVal(array, info);
info = setMinMaxWidthHeightRelatedVal(array, info);
info = setPaddingRelatedVal(array, info);
array.recycle();
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "constructed: " info);
}
return info;
}
private static PercentLayoutInfo setWidthAndHeightVal(TypedArray array, PercentLayoutInfo info)
{
PercentLayoutInfo.PercentVal percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_widthPercent, true);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent width: " percentVal.percent);
}
info = checkForInfoExists(info);
info.widthPercent = percentVal;
}
percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_heightPercent, false);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent height: " percentVal.percent);
}
info = checkForInfoExists(info);
info.heightPercent = percentVal;
}
return info;
}
private static PercentLayoutInfo setTextSizeSupportVal(TypedArray array, PercentLayoutInfo info)
{
//textSizePercent 榛樿浠ラ珮搴︿綔涓哄熀鍑�
PercentLayoutInfo.PercentVal percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_textSizePercent, false);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent text size: " percentVal.percent);
}
info = checkForInfoExists(info);
info.textSizePercent = percentVal;
}
return info;
}
private static PercentLayoutInfo setMinMaxWidthHeightRelatedVal(TypedArray array, PercentLayoutInfo info)
{
//maxWidth
PercentLayoutInfo.PercentVal percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_maxWidthPercent,
true);
if (percentVal != null)
{
checkForInfoExists(info);
info.maxWidthPercent = percentVal;
}
//maxHeight
percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_maxHeightPercent,
false);
if (percentVal != null)
{
checkForInfoExists(info);
info.maxHeightPercent = percentVal;
}
//minWidth
percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_minWidthPercent,
true);
if (percentVal != null)
{
checkForInfoExists(info);
info.minWidthPercent = percentVal;
}
//minHeight
percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_minHeightPercent,
false);
if (percentVal != null)
{
checkForInfoExists(info);
info.minHeightPercent = percentVal;
}
return info;
}
private static PercentLayoutInfo setMarginRelatedVal(TypedArray array, PercentLayoutInfo info)
{
//榛樿margin鍙傝�冨搴�
PercentLayoutInfo.PercentVal percentVal =
getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_marginPercent,
true);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent margin: " percentVal.percent);
}
info = checkForInfoExists(info);
info.leftMarginPercent = percentVal;
info.topMarginPercent = percentVal;
info.rightMarginPercent = percentVal;
info.bottomMarginPercent = percentVal;
}
percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_marginLeftPercent, true);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent left margin: " percentVal.percent);
}
info = checkForInfoExists(info);
info.leftMarginPercent = percentVal;
}
percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_marginTopPercent, false);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent top margin: " percentVal.percent);
}
info = checkForInfoExists(info);
info.topMarginPercent = percentVal;
}
percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_marginRightPercent, true);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent right margin: " percentVal.percent);
}
info = checkForInfoExists(info);
info.rightMarginPercent = percentVal;
}
percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_marginBottomPercent, false);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent bottom margin: " percentVal.percent);
}
info = checkForInfoExists(info);
info.bottomMarginPercent = percentVal;
}
percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_marginStartPercent, true);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent start margin: " percentVal.percent);
}
info = checkForInfoExists(info);
info.startMarginPercent = percentVal;
}
percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_marginEndPercent, true);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent end margin: " percentVal.percent);
}
info = checkForInfoExists(info);
info.endMarginPercent = percentVal;
}
return info;
}
/**
* 璁剧疆paddingPercent鐩稿叧灞炴��
*
* @param array
* @param info
*/
private static PercentLayoutInfo setPaddingRelatedVal(TypedArray array, PercentLayoutInfo info)
{
//榛樿padding浠ュ搴︿负鏍囧噯
PercentLayoutInfo.PercentVal percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_paddingPercent,
true);
if (percentVal != null)
{
info = checkForInfoExists(info);
info.paddingLeftPercent = percentVal;
info.paddingRightPercent = percentVal;
info.paddingBottomPercent = percentVal;
info.paddingTopPercent = percentVal;
}
percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_paddingLeftPercent,
true);
if (percentVal != null)
{
info = checkForInfoExists(info);
info.paddingLeftPercent = percentVal;
}
percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_paddingRightPercent,
true);
if (percentVal != null)
{
info = checkForInfoExists(info);
info.paddingRightPercent = percentVal;
}
percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_paddingTopPercent,
true);
if (percentVal != null)
{
info = checkForInfoExists(info);
info.paddingTopPercent = percentVal;
}
percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_paddingBottomPercent,
true);
if (percentVal != null)
{
info = checkForInfoExists(info);
info.paddingBottomPercent = percentVal;
}
return info;
}
private static PercentLayoutInfo.PercentVal getPercentVal(TypedArray array, int index, boolean baseWidth)
{
String sizeStr = array.getString(index);
PercentLayoutInfo.PercentVal percentVal = getPercentVal(sizeStr, baseWidth);
return percentVal;
}
private static PercentLayoutInfo checkForInfoExists(PercentLayoutInfo info)
{
info = info != null ? info : new PercentLayoutInfo();
return info;
}
private static final String REGEX_PERCENT = "^(([0-9] )([.]([0-9] ))?|([.]([0-9] ))?)%([s]?[wh]?)$";
/**
* widthStr to PercentVal
* <br/>
* eg: 35%w => new PercentVal(35, true)
*
* @param percentStr
* @param isOnWidth
* @return
*/
private static PercentLayoutInfo.PercentVal getPercentVal(String percentStr, boolean isOnWidth)
{
//valid param
if (percentStr == null)
{
return null;
}
Pattern p = Pattern.compile(REGEX_PERCENT);
Matcher matcher = p.matcher(percentStr);
if (!matcher.matches())
{
throw new RuntimeException("the value of layout_xxxPercent invalid! ==>" percentStr);
}
int len = percentStr.length();
//extract the float value
String floatVal = matcher.group(1);
String lastAlpha = percentStr.substring(len - 1);
float percent = Float.parseFloat(floatVal) / 100f;
PercentLayoutInfo.PercentVal percentVal = new PercentLayoutInfo.PercentVal();
percentVal.percent = percent;
if (percentStr.endsWith(PercentLayoutInfo.BASEMODE.SW))
{
percentVal.basemode = PercentLayoutInfo.BASEMODE.BASE_SCREEN_WIDTH;
} else if (percentStr.endsWith(PercentLayoutInfo.BASEMODE.SH))
{
percentVal.basemode = PercentLayoutInfo.BASEMODE.BASE_SCREEN_HEIGHT;
} else if (percentStr.endsWith(PercentLayoutInfo.BASEMODE.PERCENT))
{
if (isOnWidth)
{
percentVal.basemode = PercentLayoutInfo.BASEMODE.BASE_WIDTH;
} else
{
percentVal.basemode = PercentLayoutInfo.BASEMODE.BASE_HEIGHT;
}
} else if (percentStr.endsWith(PercentLayoutInfo.BASEMODE.W))
{
percentVal.basemode = PercentLayoutInfo.BASEMODE.BASE_WIDTH;
} else if (percentStr.endsWith(PercentLayoutInfo.BASEMODE.H))
{
percentVal.basemode = PercentLayoutInfo.BASEMODE.BASE_HEIGHT;
} else
{
throw new IllegalArgumentException("the " percentStr " must be endWith [%|w|h|sw|sh]");
}
return percentVal;
}
/**
* Iterates over children and restores their original dimensions that were changed for
* percentage values. Calling this method only makes sense if you previously called
* {@link PercentLayoutHelper#adjustChildren(int, int)}.
*/
public void restoreOriginalParams()
{
for (int i = 0, N = mHost.getChildCount(); i < N; i )
{
View view = mHost.getChildAt(i);
ViewGroup.LayoutParams params = view.getLayoutParams();
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "should restore " view " " params);
}
if (params instanceof PercentLayoutParams)
{
PercentLayoutInfo info =
((PercentLayoutParams) params).getPercentLayoutInfo();
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "using " info);
}
if (info != null)
{
if (params instanceof ViewGroup.MarginLayoutParams)
{
info.restoreMarginLayoutParams((ViewGroup.MarginLayoutParams) params);
} else
{
info.restoreLayoutParams(params);
}
}
}
}
}
/**
* Iterates over children and checks if any of them would like to get more space than it
* received through the percentage dimension.
* <p/>
* If you are building a layout that supports percentage dimensions you are encouraged to take
* advantage of this method. The developer should be able to specify that a child should be
* remeasured by adding normal dimension attribute with {@code wrap_content} value. For example
* he might specify child's attributes as {@code app:layout_widthPercent="60%p"} and
* {@code android:layout_width="wrap_content"}. In this case if the child receives too little
* space, it will be remeasured with width set to {@code WRAP_CONTENT}.
*
* @return True if the measure phase needs to be rerun because one of the children would like
* to receive more space.
*/
public boolean handleMeasuredStateTooSmall()
{
boolean needsSecondMeasure = false;
for (int i = 0, N = mHost.getChildCount(); i < N; i )
{
View view = mHost.getChildAt(i);
ViewGroup.LayoutParams params = view.getLayoutParams();
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "should handle measured state too small " view " " params);
}
if (params instanceof PercentLayoutParams)
{
PercentLayoutInfo info =
((PercentLayoutParams) params).getPercentLayoutInfo();
if (info != null)
{
if (shouldHandleMeasuredWidthTooSmall(view, info))
{
needsSecondMeasure = true;
params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
}
if (shouldHandleMeasuredHeightTooSmall(view, info))
{
needsSecondMeasure = true;
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
}
}
}
}
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "should trigger second measure pass: " needsSecondMeasure);
}
return needsSecondMeasure;
}
private static boolean shouldHandleMeasuredWidthTooSmall(View view, PercentLayoutInfo info)
{
int state = ViewCompat.getMeasuredWidthAndState(view) & ViewCompat.MEASURED_STATE_MASK;
if (info == null || info.widthPercent == null)
{
return false ;
}
return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.widthPercent.percent >= 0 &&
info.mPreservedParams.width == ViewGroup.LayoutParams.WRAP_CONTENT;
}
private static boolean shouldHandleMeasuredHeightTooSmall(View view, PercentLayoutInfo info)
{
int state = ViewCompat.getMeasuredHeightAndState(view) & ViewCompat.MEASURED_STATE_MASK;
if (info == null || info.heightPercent == null)
{
return false ;
}
return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.heightPercent.percent >= 0 &&
info.mPreservedParams.height == ViewGroup.LayoutParams.WRAP_CONTENT;
}
/**
* Container for information about percentage dimensions and margins. It acts as an extension
* for {@code LayoutParams}.
*/
public static class PercentLayoutInfo
{
private enum BASEMODE
{
BASE_WIDTH, BASE_HEIGHT, BASE_SCREEN_WIDTH, BASE_SCREEN_HEIGHT;
/**
* width_parent
*/
public static final String PERCENT = "%";
/**
* width_parent
*/
public static final String W = "w";
/**
* height_parent
*/
public static final String H = "h";
/**
* width_screen
*/
public static final String SW = "sw";
/**
* height_screen
*/
public static final String SH = "sh";
}
public static class PercentVal
{
public float percent = -1;
public BASEMODE basemode;
public PercentVal()
{
}
public PercentVal(float percent, BASEMODE baseMode)
{
this.percent = percent;
this.basemode = baseMode;
}
@Override
public String toString()
{
return "PercentVal{"
"percent=" percent
", basemode=" basemode.name()
'}';
}
}
public PercentVal widthPercent;
public PercentVal heightPercent;
public PercentVal leftMarginPercent;
public PercentVal topMarginPercent;
public PercentVal rightMarginPercent;
public PercentVal bottomMarginPercent;
public PercentVal startMarginPercent;
public PercentVal endMarginPercent;
public PercentVal textSizePercent;
//1.0.4 those attr for some views' setMax/min Height/Width method
public PercentVal maxWidthPercent;
public PercentVal maxHeightPercent;
public PercentVal minWidthPercent;
public PercentVal minHeightPercent;
//1.0.6 add padding supprot
public PercentVal paddingLeftPercent;
public PercentVal paddingRightPercent;
public PercentVal paddingTopPercent;
public PercentVal paddingBottomPercent;
/* package */ final ViewGroup.MarginLayoutParams mPreservedParams;
public PercentLayoutInfo()
{
mPreservedParams = new ViewGroup.MarginLayoutParams(0, 0);
}
/**
* Fills {@code ViewGroup.LayoutParams} dimensions based on percentage values.
*/
public void fillLayoutParams(ViewGroup.LayoutParams params, int widthHint,
int heightHint)
{
// Preserve the original layout params, so we can restore them after the measure step.
mPreservedParams.width = params.width;
mPreservedParams.height = params.height;
if (widthPercent != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, widthPercent.basemode);
params.width = (int) (base * widthPercent.percent);
}
if (heightPercent != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, heightPercent.basemode);
params.height = (int) (base * heightPercent.percent);
}
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "after fillLayoutParams: (" params.width ", " params.height ")");
}
}
/**
* Fills {@code ViewGroup.MarginLayoutParams} dimensions and margins based on percentage
* values.
*/
public void fillMarginLayoutParams(ViewGroup.MarginLayoutParams params, int widthHint,
int heightHint)
{
fillLayoutParams(params, widthHint, heightHint);
// Preserver the original margins, so we can restore them after the measure step.
mPreservedParams.leftMargin = params.leftMargin;
mPreservedParams.topMargin = params.topMargin;
mPreservedParams.rightMargin = params.rightMargin;
mPreservedParams.bottomMargin = params.bottomMargin;
MarginLayoutParamsCompat.setMarginStart(mPreservedParams,
MarginLayoutParamsCompat.getMarginStart(params));
MarginLayoutParamsCompat.setMarginEnd(mPreservedParams,
MarginLayoutParamsCompat.getMarginEnd(params));
if (leftMarginPercent != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, leftMarginPercent.basemode);
params.leftMargin = (int) (base * leftMarginPercent.percent);
}
if (topMarginPercent != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, topMarginPercent.basemode);
params.topMargin = (int) (base * topMarginPercent.percent);
}
if (rightMarginPercent != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, rightMarginPercent.basemode);
params.rightMargin = (int) (base * rightMarginPercent.percent);
}
if (bottomMarginPercent != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, bottomMarginPercent.basemode);
params.bottomMargin = (int) (base * bottomMarginPercent.percent);
}
if (startMarginPercent != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, startMarginPercent.basemode);
MarginLayoutParamsCompat.setMarginStart(params,
(int) (base * startMarginPercent.percent));
}
if (endMarginPercent != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, endMarginPercent.basemode);
MarginLayoutParamsCompat.setMarginEnd(params,
(int) (base * endMarginPercent.percent));
}
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "after fillMarginLayoutParams: (" params.width ", " params.height
")");
}
}
@Override
public String toString()
{
return "PercentLayoutInfo{"
"widthPercent=" widthPercent
", heightPercent=" heightPercent
", leftMarginPercent=" leftMarginPercent
", topMarginPercent=" topMarginPercent
", rightMarginPercent=" rightMarginPercent
", bottomMarginPercent=" bottomMarginPercent
", startMarginPercent=" startMarginPercent
", endMarginPercent=" endMarginPercent
", textSizePercent=" textSizePercent
", maxWidthPercent=" maxWidthPercent
", maxHeightPercent=" maxHeightPercent
", minWidthPercent=" minWidthPercent
", minHeightPercent=" minHeightPercent
", paddingLeftPercent=" paddingLeftPercent
", paddingRightPercent=" paddingRightPercent
", paddingTopPercent=" paddingTopPercent
", paddingBottomPercent=" paddingBottomPercent
", mPreservedParams=" mPreservedParams
'}';
}
/**
* Restores original dimensions and margins after they were changed for percentage based
* values. Calling this method only makes sense if you previously called
* {@link PercentLayoutHelper.PercentLayoutInfo#fillMarginLayoutParams}.
*/
public void restoreMarginLayoutParams(ViewGroup.MarginLayoutParams params)
{
restoreLayoutParams(params);
params.leftMargin = mPreservedParams.leftMargin;
params.topMargin = mPreservedParams.topMargin;
params.rightMargin = mPreservedParams.rightMargin;
params.bottomMargin = mPreservedParams.bottomMargin;
MarginLayoutParamsCompat.setMarginStart(params,
MarginLayoutParamsCompat.getMarginStart(mPreservedParams));
MarginLayoutParamsCompat.setMarginEnd(params,
MarginLayoutParamsCompat.getMarginEnd(mPreservedParams));
}
/**
* Restores original dimensions after they were changed for percentage based values. Calling
* this method only makes sense if you previously called
* {@link PercentLayoutHelper.PercentLayoutInfo#fillLayoutParams}.
*/
public void restoreLayoutParams(ViewGroup.LayoutParams params)
{
params.width = mPreservedParams.width;
params.height = mPreservedParams.height;
}
}
/**
* If a layout wants to support percentage based dimensions and use this helper class, its
* {@code LayoutParams} subclass must implement this interface.
* <p/>
* Your {@code LayoutParams} subclass should contain an instance of {@code PercentLayoutInfo}
* and the implementation of this interface should be a simple accessor.
*/
public interface PercentLayoutParams
{
PercentLayoutInfo getPercentLayoutInfo();
}
}
【实例截图】
【核心代码】
<android.support.percent.PercentRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" > <View android:id="@ id/top_left" android:layout_width="0dp" android:layout_height="0dp" android:layout_alignParentTop="true" app:layout_heightPercent="20%" app:layout_widthPercent="70%" android:background="#ff44aacc" /> <View android:id="@ id/top_right" android:layout_width="0dp" android:layout_height="0dp" android:layout_alignParentTop="true" android:layout_toRightOf="@ id/top_left" app:layout_heightPercent="20%" app:layout_widthPercent="30%" android:background="#ffe40000" /> <android.support.percent.PercentFrameLayout android:id="@ id/bottom" android:layout_width="match_parent" android:layout_height="0dp" android:layout_below="@ id/top_left" android:layout_gravity="center" app:layout_heightPercent="80%" android:background="#ff00ff22" > <android.support.percent.PercentLinearLayout android:layout_width="fill_parent" android:layout_height="0dp" app:layout_heightPercent="100%" app:layout_marginTopPercent="10%" android:background="@android:color/white" android:orientation="vertical" > <View android:layout_width="0dp" android:layout_height="0dp" app:layout_heightPercent="30%" app:layout_widthPercent="10%" android:background="#ff44ccdd" /> <View android:layout_width="0dp" android:layout_height="0dp" app:layout_heightPercent="30%" app:layout_widthPercent="90%" android:background="#ffe400dd" /> <TextView android:layout_width="0dp" android:layout_height="0dp" app:layout_heightPercent="20%" app:layout_widthPercent="90%" android:background="#ccc" android:text="margin 15% of w" app:layout_marginPercent="5%h" /> </android.support.percent.PercentLinearLayout> </android.support.percent.PercentFrameLayout> </android.support.percent.PercentRelativeLayout>
package com.eclipse_android_percent_support; import android.app.Activity; import android.os.Bundle; import android.view.Menu; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
/*
* Copyright (C) 2015 The Android Open Source Project*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zhy.android.percent.support;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.v4.view.MarginLayoutParamsCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.TextView;
import com.example.testpercentextends.R;
/**
* Helper for layouts that want to support percentage based dimensions.
* <p/>
* <p>This class collects utility methods that are involved in extracting percentage based dimension
* attributes and applying them to ViewGroup's children. If you would like to implement a layout
* that supports percentage based dimensions, you need to take several steps:
* <p/>
* <ol>
* <li> You need a {@link ViewGroup.LayoutParams} subclass in your ViewGroup that implements
* {@link com.zhy.android.percent.support.PercentLayoutHelper.PercentLayoutParams}.
* <li> In your {@code LayoutParams(Context c, AttributeSet attrs)} constructor create an instance
* of {@link PercentLayoutHelper.PercentLayoutInfo} by calling
* {@link PercentLayoutHelper#getPercentLayoutInfo(Context, AttributeSet)}. Return this
* object from {@code public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo()}
* method that you implemented for {@link com.zhy.android.percent.support.PercentLayoutHelper.PercentLayoutParams} interface.
* <li> Override
* {@link ViewGroup.LayoutParams#setBaseAttributes(TypedArray, int, int)}
* with a single line implementation {@code PercentLayoutHelper.fetchWidthAndHeight(this, a,
* widthAttr, heightAttr);}
* <li> In your ViewGroup override {@link ViewGroup#generateLayoutParams(AttributeSet)} to return
* your LayoutParams.
* <li> In your {@link ViewGroup#onMeasure(int, int)} override, you need to implement following
* pattern:
* <pre class="prettyprint">
* protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
* mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec);
* super.onMeasure(widthMeasureSpec, heightMeasureSpec);
* if (mHelper.handleMeasuredStateTooSmall()) {
* super.onMeasure(widthMeasureSpec, heightMeasureSpec);
* }
* }
* </pre>
* <li>In your {@link ViewGroup#onLayout(boolean, int, int, int, int)} override, you need to
* implement following pattern:
* <pre class="prettyprint">
* protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
* super.onLayout(changed, left, top, right, bottom);
* mHelper.restoreOriginalParams();
* }
* </pre>
* </ol>
*/
public class PercentLayoutHelper
{
private static final String TAG = "PercentLayout";
private final ViewGroup mHost;
private static int mWidthScreen;
private static int mHeightScreen;
public PercentLayoutHelper(ViewGroup host)
{
mHost = host;
getScreenSize();
}
private void getScreenSize()
{
WindowManager wm = (WindowManager) mHost.getContext().getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
mWidthScreen = outMetrics.widthPixels;
mHeightScreen = outMetrics.heightPixels;
}
/**
* Helper method to be called from {@link ViewGroup.LayoutParams#setBaseAttributes} override
* that reads layout_width and layout_height attribute values without throwing an exception if
* they aren't present.
*/
public static void fetchWidthAndHeight(ViewGroup.LayoutParams params, TypedArray array,
int widthAttr, int heightAttr)
{
params.width = array.getLayoutDimension(widthAttr, 0);
params.height = array.getLayoutDimension(heightAttr, 0);
}
/**
* Iterates over children and changes their width and height to one calculated from percentage
* values.
*
* @param widthMeasureSpec Width MeasureSpec of the parent ViewGroup.
* @param heightMeasureSpec Height MeasureSpec of the parent ViewGroup.
*/
public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec)
{
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "adjustChildren: " mHost " widthMeasureSpec: "
View.MeasureSpec.toString(widthMeasureSpec) " heightMeasureSpec: "
View.MeasureSpec.toString(heightMeasureSpec));
}
int widthHint = View.MeasureSpec.getSize(widthMeasureSpec);
int heightHint = View.MeasureSpec.getSize(heightMeasureSpec);
if (Log.isLoggable(TAG, Log.DEBUG))
Log.d(TAG, "widthHint = " widthHint " , heightHint = " heightHint);
for (int i = 0, N = mHost.getChildCount(); i < N; i )
{
View view = mHost.getChildAt(i);
ViewGroup.LayoutParams params = view.getLayoutParams();
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "should adjust " view " " params);
}
if (params instanceof PercentLayoutParams)
{
PercentLayoutInfo info =
((PercentLayoutParams) params).getPercentLayoutInfo();
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "using " info);
}
if (info != null)
{
supportTextSize(widthHint, heightHint, view, info);
supportPadding(widthHint, heightHint, view, info);
supportMinOrMaxDimesion(widthHint, heightHint, view, info);
if (params instanceof ViewGroup.MarginLayoutParams)
{
info.fillMarginLayoutParams((ViewGroup.MarginLayoutParams) params,
widthHint, heightHint);
} else
{
info.fillLayoutParams(params, widthHint, heightHint);
}
}
}
}
}
private void supportPadding(int widthHint, int heightHint, View view, PercentLayoutInfo info)
{
int left = view.getPaddingLeft(), right = view.getPaddingRight(), top = view.getPaddingTop(), bottom = view.getPaddingBottom();
PercentLayoutInfo.PercentVal percentVal = info.paddingLeftPercent;
if (percentVal != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, percentVal.basemode);
left = (int) (base * percentVal.percent);
}
percentVal = info.paddingRightPercent;
if (percentVal != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, percentVal.basemode);
right = (int) (base * percentVal.percent);
}
percentVal = info.paddingTopPercent;
if (percentVal != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, percentVal.basemode);
top = (int) (base * percentVal.percent);
}
percentVal = info.paddingBottomPercent;
if (percentVal != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, percentVal.basemode);
bottom = (int) (base * percentVal.percent);
}
view.setPadding(left, top, right, bottom);
}
private void supportMinOrMaxDimesion(int widthHint, int heightHint, View view, PercentLayoutInfo info)
{
try
{
Class clazz = view.getClass();
invokeMethod("setMaxWidth", widthHint, heightHint, view, clazz, info.maxWidthPercent);
invokeMethod("setMaxHeight", widthHint, heightHint, view, clazz, info.maxHeightPercent);
invokeMethod("setMinWidth", widthHint, heightHint, view, clazz, info.minWidthPercent);
invokeMethod("setMinHeight", widthHint, heightHint, view, clazz, info.minHeightPercent);
} catch (NoSuchMethodException e)
{
e.printStackTrace();
} catch (InvocationTargetException e)
{
e.printStackTrace();
} catch (IllegalAccessException e)
{
e.printStackTrace();
}
}
private void invokeMethod(String methodName, int widthHint, int heightHint, View view, Class clazz, PercentLayoutInfo.PercentVal percentVal) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
{
if (Log.isLoggable(TAG, Log.DEBUG))
Log.d(TAG, methodName " ==> " percentVal);
if (percentVal != null)
{
Method setMaxWidthMethod = clazz.getMethod(methodName, int.class);
setMaxWidthMethod.setAccessible(true);
int base = getBaseByModeAndVal(widthHint, heightHint, percentVal.basemode);
setMaxWidthMethod.invoke(view, (int) (base * percentVal.percent));
}
}
private void supportTextSize(int widthHint, int heightHint, View view, PercentLayoutInfo info)
{
//textsize percent support
PercentLayoutInfo.PercentVal textSizePercent = info.textSizePercent;
if (textSizePercent == null) return;
int base = getBaseByModeAndVal(widthHint, heightHint, textSizePercent.basemode);
float textSize = (int) (base * textSizePercent.percent);
//Button 鍜� EditText 鏄疶extView鐨勫瓙绫�
if (view instanceof TextView)
{
((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}
}
private static int getBaseByModeAndVal(int widthHint, int heightHint, PercentLayoutInfo.BASEMODE basemode)
{
switch (basemode)
{
case BASE_HEIGHT:
return heightHint;
case BASE_WIDTH:
return widthHint;
case BASE_SCREEN_WIDTH:
return mWidthScreen;
case BASE_SCREEN_HEIGHT:
return mHeightScreen;
}
return 0;
}
/**
* Constructs a PercentLayoutInfo from attributes associated with a View. Call this method from
* {@code LayoutParams(Context c, AttributeSet attrs)} constructor.
*/
public static PercentLayoutInfo getPercentLayoutInfo(Context context,
AttributeSet attrs)
{
PercentLayoutInfo info = null;
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PercentLayout_Layout);
info = setWidthAndHeightVal(array, info);
info = setMarginRelatedVal(array, info);
info = setTextSizeSupportVal(array, info);
info = setMinMaxWidthHeightRelatedVal(array, info);
info = setPaddingRelatedVal(array, info);
array.recycle();
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "constructed: " info);
}
return info;
}
private static PercentLayoutInfo setWidthAndHeightVal(TypedArray array, PercentLayoutInfo info)
{
PercentLayoutInfo.PercentVal percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_widthPercent, true);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent width: " percentVal.percent);
}
info = checkForInfoExists(info);
info.widthPercent = percentVal;
}
percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_heightPercent, false);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent height: " percentVal.percent);
}
info = checkForInfoExists(info);
info.heightPercent = percentVal;
}
return info;
}
private static PercentLayoutInfo setTextSizeSupportVal(TypedArray array, PercentLayoutInfo info)
{
//textSizePercent 榛樿浠ラ珮搴︿綔涓哄熀鍑�
PercentLayoutInfo.PercentVal percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_textSizePercent, false);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent text size: " percentVal.percent);
}
info = checkForInfoExists(info);
info.textSizePercent = percentVal;
}
return info;
}
private static PercentLayoutInfo setMinMaxWidthHeightRelatedVal(TypedArray array, PercentLayoutInfo info)
{
//maxWidth
PercentLayoutInfo.PercentVal percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_maxWidthPercent,
true);
if (percentVal != null)
{
checkForInfoExists(info);
info.maxWidthPercent = percentVal;
}
//maxHeight
percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_maxHeightPercent,
false);
if (percentVal != null)
{
checkForInfoExists(info);
info.maxHeightPercent = percentVal;
}
//minWidth
percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_minWidthPercent,
true);
if (percentVal != null)
{
checkForInfoExists(info);
info.minWidthPercent = percentVal;
}
//minHeight
percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_minHeightPercent,
false);
if (percentVal != null)
{
checkForInfoExists(info);
info.minHeightPercent = percentVal;
}
return info;
}
private static PercentLayoutInfo setMarginRelatedVal(TypedArray array, PercentLayoutInfo info)
{
//榛樿margin鍙傝�冨搴�
PercentLayoutInfo.PercentVal percentVal =
getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_marginPercent,
true);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent margin: " percentVal.percent);
}
info = checkForInfoExists(info);
info.leftMarginPercent = percentVal;
info.topMarginPercent = percentVal;
info.rightMarginPercent = percentVal;
info.bottomMarginPercent = percentVal;
}
percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_marginLeftPercent, true);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent left margin: " percentVal.percent);
}
info = checkForInfoExists(info);
info.leftMarginPercent = percentVal;
}
percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_marginTopPercent, false);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent top margin: " percentVal.percent);
}
info = checkForInfoExists(info);
info.topMarginPercent = percentVal;
}
percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_marginRightPercent, true);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent right margin: " percentVal.percent);
}
info = checkForInfoExists(info);
info.rightMarginPercent = percentVal;
}
percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_marginBottomPercent, false);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent bottom margin: " percentVal.percent);
}
info = checkForInfoExists(info);
info.bottomMarginPercent = percentVal;
}
percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_marginStartPercent, true);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent start margin: " percentVal.percent);
}
info = checkForInfoExists(info);
info.startMarginPercent = percentVal;
}
percentVal = getPercentVal(array, R.styleable.PercentLayout_Layout_layout_marginEndPercent, true);
if (percentVal != null)
{
if (Log.isLoggable(TAG, Log.VERBOSE))
{
Log.v(TAG, "percent end margin: " percentVal.percent);
}
info = checkForInfoExists(info);
info.endMarginPercent = percentVal;
}
return info;
}
/**
* 璁剧疆paddingPercent鐩稿叧灞炴��
*
* @param array
* @param info
*/
private static PercentLayoutInfo setPaddingRelatedVal(TypedArray array, PercentLayoutInfo info)
{
//榛樿padding浠ュ搴︿负鏍囧噯
PercentLayoutInfo.PercentVal percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_paddingPercent,
true);
if (percentVal != null)
{
info = checkForInfoExists(info);
info.paddingLeftPercent = percentVal;
info.paddingRightPercent = percentVal;
info.paddingBottomPercent = percentVal;
info.paddingTopPercent = percentVal;
}
percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_paddingLeftPercent,
true);
if (percentVal != null)
{
info = checkForInfoExists(info);
info.paddingLeftPercent = percentVal;
}
percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_paddingRightPercent,
true);
if (percentVal != null)
{
info = checkForInfoExists(info);
info.paddingRightPercent = percentVal;
}
percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_paddingTopPercent,
true);
if (percentVal != null)
{
info = checkForInfoExists(info);
info.paddingTopPercent = percentVal;
}
percentVal = getPercentVal(array,
R.styleable.PercentLayout_Layout_layout_paddingBottomPercent,
true);
if (percentVal != null)
{
info = checkForInfoExists(info);
info.paddingBottomPercent = percentVal;
}
return info;
}
private static PercentLayoutInfo.PercentVal getPercentVal(TypedArray array, int index, boolean baseWidth)
{
String sizeStr = array.getString(index);
PercentLayoutInfo.PercentVal percentVal = getPercentVal(sizeStr, baseWidth);
return percentVal;
}
private static PercentLayoutInfo checkForInfoExists(PercentLayoutInfo info)
{
info = info != null ? info : new PercentLayoutInfo();
return info;
}
private static final String REGEX_PERCENT = "^(([0-9] )([.]([0-9] ))?|([.]([0-9] ))?)%([s]?[wh]?)$";
/**
* widthStr to PercentVal
* <br/>
* eg: 35%w => new PercentVal(35, true)
*
* @param percentStr
* @param isOnWidth
* @return
*/
private static PercentLayoutInfo.PercentVal getPercentVal(String percentStr, boolean isOnWidth)
{
//valid param
if (percentStr == null)
{
return null;
}
Pattern p = Pattern.compile(REGEX_PERCENT);
Matcher matcher = p.matcher(percentStr);
if (!matcher.matches())
{
throw new RuntimeException("the value of layout_xxxPercent invalid! ==>" percentStr);
}
int len = percentStr.length();
//extract the float value
String floatVal = matcher.group(1);
String lastAlpha = percentStr.substring(len - 1);
float percent = Float.parseFloat(floatVal) / 100f;
PercentLayoutInfo.PercentVal percentVal = new PercentLayoutInfo.PercentVal();
percentVal.percent = percent;
if (percentStr.endsWith(PercentLayoutInfo.BASEMODE.SW))
{
percentVal.basemode = PercentLayoutInfo.BASEMODE.BASE_SCREEN_WIDTH;
} else if (percentStr.endsWith(PercentLayoutInfo.BASEMODE.SH))
{
percentVal.basemode = PercentLayoutInfo.BASEMODE.BASE_SCREEN_HEIGHT;
} else if (percentStr.endsWith(PercentLayoutInfo.BASEMODE.PERCENT))
{
if (isOnWidth)
{
percentVal.basemode = PercentLayoutInfo.BASEMODE.BASE_WIDTH;
} else
{
percentVal.basemode = PercentLayoutInfo.BASEMODE.BASE_HEIGHT;
}
} else if (percentStr.endsWith(PercentLayoutInfo.BASEMODE.W))
{
percentVal.basemode = PercentLayoutInfo.BASEMODE.BASE_WIDTH;
} else if (percentStr.endsWith(PercentLayoutInfo.BASEMODE.H))
{
percentVal.basemode = PercentLayoutInfo.BASEMODE.BASE_HEIGHT;
} else
{
throw new IllegalArgumentException("the " percentStr " must be endWith [%|w|h|sw|sh]");
}
return percentVal;
}
/**
* Iterates over children and restores their original dimensions that were changed for
* percentage values. Calling this method only makes sense if you previously called
* {@link PercentLayoutHelper#adjustChildren(int, int)}.
*/
public void restoreOriginalParams()
{
for (int i = 0, N = mHost.getChildCount(); i < N; i )
{
View view = mHost.getChildAt(i);
ViewGroup.LayoutParams params = view.getLayoutParams();
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "should restore " view " " params);
}
if (params instanceof PercentLayoutParams)
{
PercentLayoutInfo info =
((PercentLayoutParams) params).getPercentLayoutInfo();
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "using " info);
}
if (info != null)
{
if (params instanceof ViewGroup.MarginLayoutParams)
{
info.restoreMarginLayoutParams((ViewGroup.MarginLayoutParams) params);
} else
{
info.restoreLayoutParams(params);
}
}
}
}
}
/**
* Iterates over children and checks if any of them would like to get more space than it
* received through the percentage dimension.
* <p/>
* If you are building a layout that supports percentage dimensions you are encouraged to take
* advantage of this method. The developer should be able to specify that a child should be
* remeasured by adding normal dimension attribute with {@code wrap_content} value. For example
* he might specify child's attributes as {@code app:layout_widthPercent="60%p"} and
* {@code android:layout_width="wrap_content"}. In this case if the child receives too little
* space, it will be remeasured with width set to {@code WRAP_CONTENT}.
*
* @return True if the measure phase needs to be rerun because one of the children would like
* to receive more space.
*/
public boolean handleMeasuredStateTooSmall()
{
boolean needsSecondMeasure = false;
for (int i = 0, N = mHost.getChildCount(); i < N; i )
{
View view = mHost.getChildAt(i);
ViewGroup.LayoutParams params = view.getLayoutParams();
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "should handle measured state too small " view " " params);
}
if (params instanceof PercentLayoutParams)
{
PercentLayoutInfo info =
((PercentLayoutParams) params).getPercentLayoutInfo();
if (info != null)
{
if (shouldHandleMeasuredWidthTooSmall(view, info))
{
needsSecondMeasure = true;
params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
}
if (shouldHandleMeasuredHeightTooSmall(view, info))
{
needsSecondMeasure = true;
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
}
}
}
}
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "should trigger second measure pass: " needsSecondMeasure);
}
return needsSecondMeasure;
}
private static boolean shouldHandleMeasuredWidthTooSmall(View view, PercentLayoutInfo info)
{
int state = ViewCompat.getMeasuredWidthAndState(view) & ViewCompat.MEASURED_STATE_MASK;
if (info == null || info.widthPercent == null)
{
return false ;
}
return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.widthPercent.percent >= 0 &&
info.mPreservedParams.width == ViewGroup.LayoutParams.WRAP_CONTENT;
}
private static boolean shouldHandleMeasuredHeightTooSmall(View view, PercentLayoutInfo info)
{
int state = ViewCompat.getMeasuredHeightAndState(view) & ViewCompat.MEASURED_STATE_MASK;
if (info == null || info.heightPercent == null)
{
return false ;
}
return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.heightPercent.percent >= 0 &&
info.mPreservedParams.height == ViewGroup.LayoutParams.WRAP_CONTENT;
}
/**
* Container for information about percentage dimensions and margins. It acts as an extension
* for {@code LayoutParams}.
*/
public static class PercentLayoutInfo
{
private enum BASEMODE
{
BASE_WIDTH, BASE_HEIGHT, BASE_SCREEN_WIDTH, BASE_SCREEN_HEIGHT;
/**
* width_parent
*/
public static final String PERCENT = "%";
/**
* width_parent
*/
public static final String W = "w";
/**
* height_parent
*/
public static final String H = "h";
/**
* width_screen
*/
public static final String SW = "sw";
/**
* height_screen
*/
public static final String SH = "sh";
}
public static class PercentVal
{
public float percent = -1;
public BASEMODE basemode;
public PercentVal()
{
}
public PercentVal(float percent, BASEMODE baseMode)
{
this.percent = percent;
this.basemode = baseMode;
}
@Override
public String toString()
{
return "PercentVal{"
"percent=" percent
", basemode=" basemode.name()
'}';
}
}
public PercentVal widthPercent;
public PercentVal heightPercent;
public PercentVal leftMarginPercent;
public PercentVal topMarginPercent;
public PercentVal rightMarginPercent;
public PercentVal bottomMarginPercent;
public PercentVal startMarginPercent;
public PercentVal endMarginPercent;
public PercentVal textSizePercent;
//1.0.4 those attr for some views' setMax/min Height/Width method
public PercentVal maxWidthPercent;
public PercentVal maxHeightPercent;
public PercentVal minWidthPercent;
public PercentVal minHeightPercent;
//1.0.6 add padding supprot
public PercentVal paddingLeftPercent;
public PercentVal paddingRightPercent;
public PercentVal paddingTopPercent;
public PercentVal paddingBottomPercent;
/* package */ final ViewGroup.MarginLayoutParams mPreservedParams;
public PercentLayoutInfo()
{
mPreservedParams = new ViewGroup.MarginLayoutParams(0, 0);
}
/**
* Fills {@code ViewGroup.LayoutParams} dimensions based on percentage values.
*/
public void fillLayoutParams(ViewGroup.LayoutParams params, int widthHint,
int heightHint)
{
// Preserve the original layout params, so we can restore them after the measure step.
mPreservedParams.width = params.width;
mPreservedParams.height = params.height;
if (widthPercent != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, widthPercent.basemode);
params.width = (int) (base * widthPercent.percent);
}
if (heightPercent != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, heightPercent.basemode);
params.height = (int) (base * heightPercent.percent);
}
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "after fillLayoutParams: (" params.width ", " params.height ")");
}
}
/**
* Fills {@code ViewGroup.MarginLayoutParams} dimensions and margins based on percentage
* values.
*/
public void fillMarginLayoutParams(ViewGroup.MarginLayoutParams params, int widthHint,
int heightHint)
{
fillLayoutParams(params, widthHint, heightHint);
// Preserver the original margins, so we can restore them after the measure step.
mPreservedParams.leftMargin = params.leftMargin;
mPreservedParams.topMargin = params.topMargin;
mPreservedParams.rightMargin = params.rightMargin;
mPreservedParams.bottomMargin = params.bottomMargin;
MarginLayoutParamsCompat.setMarginStart(mPreservedParams,
MarginLayoutParamsCompat.getMarginStart(params));
MarginLayoutParamsCompat.setMarginEnd(mPreservedParams,
MarginLayoutParamsCompat.getMarginEnd(params));
if (leftMarginPercent != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, leftMarginPercent.basemode);
params.leftMargin = (int) (base * leftMarginPercent.percent);
}
if (topMarginPercent != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, topMarginPercent.basemode);
params.topMargin = (int) (base * topMarginPercent.percent);
}
if (rightMarginPercent != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, rightMarginPercent.basemode);
params.rightMargin = (int) (base * rightMarginPercent.percent);
}
if (bottomMarginPercent != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, bottomMarginPercent.basemode);
params.bottomMargin = (int) (base * bottomMarginPercent.percent);
}
if (startMarginPercent != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, startMarginPercent.basemode);
MarginLayoutParamsCompat.setMarginStart(params,
(int) (base * startMarginPercent.percent));
}
if (endMarginPercent != null)
{
int base = getBaseByModeAndVal(widthHint, heightHint, endMarginPercent.basemode);
MarginLayoutParamsCompat.setMarginEnd(params,
(int) (base * endMarginPercent.percent));
}
if (Log.isLoggable(TAG, Log.DEBUG))
{
Log.d(TAG, "after fillMarginLayoutParams: (" params.width ", " params.height
")");
}
}
@Override
public String toString()
{
return "PercentLayoutInfo{"
"widthPercent=" widthPercent
", heightPercent=" heightPercent
", leftMarginPercent=" leftMarginPercent
", topMarginPercent=" topMarginPercent
", rightMarginPercent=" rightMarginPercent
", bottomMarginPercent=" bottomMarginPercent
", startMarginPercent=" startMarginPercent
", endMarginPercent=" endMarginPercent
", textSizePercent=" textSizePercent
", maxWidthPercent=" maxWidthPercent
", maxHeightPercent=" maxHeightPercent
", minWidthPercent=" minWidthPercent
", minHeightPercent=" minHeightPercent
", paddingLeftPercent=" paddingLeftPercent
", paddingRightPercent=" paddingRightPercent
", paddingTopPercent=" paddingTopPercent
", paddingBottomPercent=" paddingBottomPercent
", mPreservedParams=" mPreservedParams
'}';
}
/**
* Restores original dimensions and margins after they were changed for percentage based
* values. Calling this method only makes sense if you previously called
* {@link PercentLayoutHelper.PercentLayoutInfo#fillMarginLayoutParams}.
*/
public void restoreMarginLayoutParams(ViewGroup.MarginLayoutParams params)
{
restoreLayoutParams(params);
params.leftMargin = mPreservedParams.leftMargin;
params.topMargin = mPreservedParams.topMargin;
params.rightMargin = mPreservedParams.rightMargin;
params.bottomMargin = mPreservedParams.bottomMargin;
MarginLayoutParamsCompat.setMarginStart(params,
MarginLayoutParamsCompat.getMarginStart(mPreservedParams));
MarginLayoutParamsCompat.setMarginEnd(params,
MarginLayoutParamsCompat.getMarginEnd(mPreservedParams));
}
/**
* Restores original dimensions after they were changed for percentage based values. Calling
* this method only makes sense if you previously called
* {@link PercentLayoutHelper.PercentLayoutInfo#fillLayoutParams}.
*/
public void restoreLayoutParams(ViewGroup.LayoutParams params)
{
params.width = mPreservedParams.width;
params.height = mPreservedParams.height;
}
}
/**
* If a layout wants to support percentage based dimensions and use this helper class, its
* {@code LayoutParams} subclass must implement this interface.
* <p/>
* Your {@code LayoutParams} subclass should contain an instance of {@code PercentLayoutInfo}
* and the implementation of this interface should be a simple accessor.
*/
public interface PercentLayoutParams
{
PercentLayoutInfo getPercentLayoutInfo();
}
}
好例子网口号:伸出你的我的手 — 分享!
小贴士
感谢您为本站写下的评论,您的评论对其它用户来说具有重要的参考价值,所以请认真填写。
- 类似“顶”、“沙发”之类没有营养的文字,对勤劳贡献的楼主来说是令人沮丧的反馈信息。
- 相信您也不想看到一排文字/表情墙,所以请不要反馈意义不大的重复字符,也请尽量不要纯表情的回复。
- 提问之前请再仔细看一遍楼主的说明,或许是您遗漏了。
- 请勿到处挖坑绊人、招贴广告。既占空间让人厌烦,又没人会搭理,于人于己都无利。
关于好例子网
本站旨在为广大IT学习爱好者提供一个非营利性互相学习交流分享平台。本站所有资源都可以被免费获取学习研究。本站资源来自网友分享,对搜索内容的合法性不具有预见性、识别性、控制性,仅供学习研究,请务必在下载后24小时内给予删除,不得用于其他任何用途,否则后果自负。基于互联网的特殊性,平台无法对用户传输的作品、信息、内容的权属或合法性、安全性、合规性、真实性、科学性、完整权、有效性等进行实质审查;无论平台是否已进行审查,用户均应自行承担因其传输的作品、信息、内容而可能或已经产生的侵权或权属纠纷等法律责任。本站所有资源不代表本站的观点或立场,基于网友分享,根据中国法律《信息网络传播权保护条例》第二十二与二十三条之规定,若资源存在侵权或相关问题请联系本站客服人员,点此联系我们。关于更多版权及免责申明参见 版权及免责申明
网友评论
我要评论