任务:缩放布局以填充所有可用空间。我创建了容器,称为 ZoomViewGroup,它测量自身内部的单个子节点,并根据其自身大小设置 scaleX 和 scaleY。
package sample.andrew.myapplication;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import org.jetbrains.annotations.NotNull;
/**
* Container class for single child to be zoomed to fill container.
*/
public class ZoomViewGroup extends ViewGroup
{
private static final String log = "mopo-zoom";
@SuppressWarnings("UnusedDeclaration")
public ZoomViewGroup(Context context)
{
super(context);
}
@SuppressWarnings("UnusedDeclaration")
public ZoomViewGroup(Context context, AttributeSet attrs){
super(context, attrs);
}
@SuppressWarnings("UnusedDeclaration")
public ZoomViewGroup(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private void checkCount() {
if(getChildCount() > 1)
{
throw new IllegalStateException("ZoomViewGroup can host only one direct child");
}
}
@Override
public void addView(@NotNull View child) {
checkCount();
super.addView(child);
}
@Override
public void addView(@NotNull View child, int index) {
checkCount();
super.addView(child, index);
}
@Override
public void addView(@NotNull View child, LayoutParams params) {
checkCount();
super.addView(child, params);
}
@Override
public void addView(@NotNull View child, int index, LayoutParams params) {
checkCount();
super.addView(child, index, params);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
View child = getChildAt(0);
int fake_size = MeasureSpec.makeMeasureSpec(3000, MeasureSpec.UNSPECIFIED);
assert child != null;
assert child.getLayoutParams() != null;
if(child.getLayoutParams().width == LayoutParams.MATCH_PARENT)
{
int w = MeasureSpec.getSize(widthMeasureSpec);
int fake_w = MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY);
// if(Log.ENABLED) Log.i(log, "measure child with width " + w + "x3000");
measureChildren(fake_w, fake_size);
// if(Log.ENABLED) Log.i(log, "measured child: " + child.getMeasuredWidth() + "x" + child.getMeasuredHeight());
}
else
{
// By using fake size we will get child measured with
// wrap_content size, so we can calculate needed zoom to fit
// child in whole space
measureChildren(fake_size, fake_size);
}
int w = MeasureSpec.getSize(widthMeasureSpec);
int h = MeasureSpec.getSize(heightMeasureSpec);
// ZoomViewGroup supports only match_parent layout params for itself,
// so we don't modify income sizes and set them directly.
setMeasuredDimension(w, h);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if(Log.ENABLED) Log.i(log, "onLayout " + l + " " + t + " " + r + " " +b);
View child = getChildAt(0);
assert child != null;
if(child.getVisibility() != GONE) {
int w = r - l;
int h = b - t;
int child_w = child.getMeasuredWidth();
int child_h = child.getMeasuredHeight();
if(child_w == w && child_h == h)
{
child.layout(l, t, r, b);
}
else
{
int dx = w - child_w;
int dy = h - child_h;
if(Log.ENABLED) Log.i(log, "dx, dy " + dx + " " + dy);
int cl,ct,cr,cb;
if(dx == 0) {
cl = 0;
cr = child_w;
} else {
cl = (w-child_w)/2;
cr = cl + child_w;
}
if(dy == 0)
{
ct = 0;
cb = child_h;
}
else
{
ct = (h - child_h)/2;
cb = ct + child_h;
}
if(Log.ENABLED) Log.i(log, "set child bounds: " +
cl + " " + ct + " " + cr + " " + cb);
child.layout(cl, ct, cr, cb);
}
}
}
@Override
public void onSizeChanged(int width, int height, int oldw, int oldh) {
super.onSizeChanged(width, height, oldw, oldh);
if(Log.ENABLED) Log.i(log, "onSizeChanged " + oldw + "x" + oldh +
" -> " + width + "x" + height);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
{
View child = getChildAt(0);
if(child != null)
{
float originScaleX = child.getScaleX();
float originScaleY = child.getScaleY();
if(originScaleX != 1 || originScaleY != 1)
{
if(Log.ENABLED) Log.i(log, "scale set: " + originScaleX + " " + originScaleY);
if(Log.ENABLED) Log.i(log, "scale size: " + (int)((originScaleX*child.getMeasuredWidth())) + "x"
+ (int)(originScaleY*child.getMeasuredHeight())
+ " [" + child.getMeasuredWidth() + "x" + child.getMeasuredHeight() + "]" );
return;
}
@SuppressWarnings("ConstantConditions")
int originWidth = child.getMeasuredWidth();
int originHeight = child.getMeasuredHeight();
if(Log.ENABLED) Log.i(log, "child size: " + originWidth + " " + originHeight);
if(originWidth > 0)
{
float zoomFactorX = findZoomCoef(width, originWidth);
float zoomFactorY = findZoomCoef(height, originHeight);
if(Log.ENABLED) Log.i(log, "calc zoom [" + zoomFactorX + ", " + zoomFactorY + "]");
child.setScaleX(zoomFactorX);
child.setScaleY(zoomFactorY);
}
}
}
}
/**
* Calculates such coefficient to meet rule:
* size = (int)(sizeToZoom*coef)
* @param size
* @param sizeToZoom
* @return coef
*/
public static float findZoomCoef(int size, int sizeToZoom)
{
float zoomFactor = (size*100/sizeToZoom)/100.0f;
float step = 0.001f;
int count = 0;
do
{
int reverse_size = (int) (sizeToZoom*zoomFactor);
if(reverse_size == size)
break;
if(reverse_size < size)
{
zoomFactor += step;
}
else
{
zoomFactor -= step;
step /= 10;
}
count++;
}
while (true);
if(Log.ENABLED) Log.i(log, "calc zoom: s,c " + step + " " + count);
return zoomFactor;
}
}
此实现适用于除 nexus 4 和 nexus 5 之外的主要设备数量。这是我第一次看到这个。通常某些东西在三星或索尼设备上不起作用,反之亦然。测试布局以显示问题:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MyActivity">
<sample.andrew.myapplication.ZoomViewGroup
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:id="@+id/test_layout">
<View android:layout_width="200dp"
android:layout_height="200dp"
android:background="#70c0"
android:id="@+id/anchor"
/>
<View android:layout_width="20dp"
android:layout_height="20dp"
android:background="#f00"
android:layout_centerInParent="true"
/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/test_anchored_layout"
android:layout_alignTop="@id/anchor"
android:layout_alignLeft="@id/anchor"
android:layout_alignRight="@id/anchor"
android:layout_alignBottom="@id/anchor">
<!--<RelativeLayout-->
<!--android:layout_width="200dp"-->
<!--android:id="@+id/test_anchored_layout"-->
<!--android:layout_height="200dp">-->
<View android:layout_width="80dp"
android:layout_height="20dp"
android:layout_centerHorizontal="true"
android:background="#00f"
/>
</RelativeLayout>
</RelativeLayout>
</sample.andrew.myapplication.ZoomViewGroup>
</RelativeLayout>
测试样本的外观:1. android studio 设计器/预览
2 它应该是怎样的以及它是如何工作的,例如在所有 Galaxy 设备上
3 它在 nexus 4 和 nexus 5 上的外观
我发现如果 test_anchored_layout 使用固定大小(注释部分)而不是布局规则,那么即使在连接上也一切正常。
连接的错误或我不明白的东西?完整的项目档案: