编辑 3:
出于历史原因,我将保留下面的原始问题。但是,我发现这个问题不仅限于FrameLayout
。因此,我更新了标题。
为了避免在此帖子中添加更多代码,我创建了一个演示该问题的示例项目,并将其上传到Google项目托管上。
问题的摘要如下:
在纵向方向上具有一定边界的可绘制对象,在改变方向并返回纵向时,不会保留设置的边界。相反,它会恢复到原始边界。尽管强制在方向更改时设置了明确的边界,但请注意,稍后在单击等操作中设置的任何边界都将被遵守。
我还上传了一个包含两个独立活动的应用程序版本来说明这个问题。
- 普通的活动,只是说明问题。
- 在
ImageView
上使用自定义BitmapDrawable
的活动-这个版本在每次更改边界时都会打印到日志中。
第二个版本清楚地显示,即使我在可绘制对象上设置了边界,这些边界也不会在onBoundsChange()
中遵守。
原始问题:
我正在使用一个FrameLayout
,其中有2个重叠的ImageView
用于显示“电池状态”图形。这是在纵向模式下。在横向模式下,我显示不同的布局(图表)。
我的问题是这样的-假设电池状态正在显示-例如30%。现在,我旋转屏幕并显示图表。当我回到纵向方向时,电池图形返回到其原始位置(即“满”)。
我尝试了各种方法来尝试弄清楚发生了什么。调试显示,“顶部”图形的边界确实按预期设置。因此,这似乎是一个失效问题。我提供了两个类和两个布局XML的代码(所有内容都经过简化),以便能够重新生成该问题。还附加了用于ImageView的占位符PNG。
有人能看出错误吗?要重现此问题,请运行应用程序,然后单击“更新”按钮。图形将会被“填充”到某个级别。然后,切换到横向模式,再切回纵向模式。图形不会记住先前的值。
该活动:
public class RotateActivity extends Activity {
private View portraitView, landscapeView;
private LayoutInflater li;
private Configuration mConfig;
private ValueIndicator indicator;
private Button btn;
private Random random = new java.util.Random();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
li = LayoutInflater.from(this);
portraitView = li.inflate(R.layout.portrait, null);
landscapeView = li.inflate(R.layout.landscape, null);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mConfig = newConfig;
initialize();
}
@Override
protected void onResume() {
mConfig = getResources().getConfiguration();
initialize();
super.onResume();
}
private void initialize(){
if(mConfig.orientation == Configuration.ORIENTATION_LANDSCAPE){
displayLandscape();
} else {
displayPortrait();
}
}
private void displayLandscape() {
setContentView(landscapeView);
}
private void displayPortrait() {
setContentView(portraitView);
btn = (Button)portraitView.findViewById(R.id.button1);
indicator = (ValueIndicator)portraitView.findViewById(R.id.valueIndicator1);
/*
* Forcing the graphic to perform redraw to its known state when we return to portrait view.
*/
indicator.updateIndicatorUi();
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
updateIndicator(random.nextInt(100));
}
});
}
private void updateIndicator(int newValue){
indicator.setPercent(newValue);
}
}
portrait.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<com.gs.kiran.trial.inval.ValueIndicator
android:id="@+id/valueIndicator1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Update" />
</LinearLayout>
ValueIndicator.java
public class ValueIndicator extends FrameLayout {
private ImageView ivFrame, ivContent;
private Drawable drFrame, drContent;
private Rect mBounds;
private int currentTop;
private int mPercent;
public ValueIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater li = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View root = li.inflate(R.layout.indicator, this, true);
ivFrame = (ImageView) root.findViewById(R.id.ivFrame);
ivContent = (ImageView) root.findViewById(R.id.ivContent);
drFrame = ivFrame.getDrawable();
drContent = ivContent.getDrawable();
mBounds = drFrame.getBounds();
Log.d(Constants.LOG_TAG, "Constructor of ValueIndicator");
}
public void setPercent(int newPercent){
this.mPercent = newPercent;
updateIndicatorUi();
}
public void updateIndicatorUi(){
Rect newBounds = new Rect(mBounds);
newBounds.top = mBounds.bottom - (int)(this.mPercent * mBounds.height() / 100);
currentTop = newBounds.top;
Log.d(Constants.LOG_TAG, "currentTop = "+currentTop);
drContent.setBounds(newBounds);
//invalidateDrawable(drContent);
invalidate();
}
}
indicator.xml(自定义视图中使用的FrameLayout的XML)
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/frameLayout1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/ivFrame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/frame" />
<ImageView
android:id="@+id/ivContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/content" />
</FrameLayout>
landscape.xml(虚拟占位符 - 能够重现问题)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Landscape" />
</LinearLayout>
AndroidManifest.xml片段:
<activity
android:label="@string/app_name"
android:name=".RotateActivity"
android:configChanges="orientation|keyboardHidden">
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
编辑
我还尝试调用setPercent()
并在displayPortraitView()
中传递保存的值-基本上是在回到纵向模式时强制更新到已知状态。 仍然没有运气。 请注意,日志告诉我绘图对象的边界是正确的。 我不明白为什么无效化不会发生。
第二次编辑:
- 在ValueIndicator.java中,我引入了一个成员变量
mPercent
,它始终存储上一个已知的百分比值。 - 更新
setPercent()
代码以更新成员变量mPercent
; 然后调用updateIndicateUi()
方法。 updateIndicatorUi()
(现在是public
方法)现在使用该状态(即mPercent
)来完成其工作。- 每当我们回到竖屏模式时,我都会调用
updateIndicatorUi()
。 这会强制电池图形更新自己。
我还更新了代码以反映这些更改。
想法是每当我们从横向模式返回纵向模式时都强制重绘和无效化。 再次强调-我确实看到电池“内容”绘图对象的边界被设置为期望值,但是UI无法跟上。
FrameLayout
显示电池状态,其中包含3个高质量的图像 - 电池“框架”,黑色的电池“背景”和绿色的电池“内容”。我在Android上研究了很多关于以最佳方式图形化显示电池状态的方法,并得出结论setBounds
是最好的方法。因此,我认为简单的自定义View
与onDraw
解决方案可能不适合我。我将尝试建议1并回报结果。 - curioustechizenImageView
更改了边界,我仍然在setContentView()
之后强制设置边界。为什么我指定的新边界没有被遵守?我注意到,setImageDrawable()
反过来调用了requestLayout()
- 为了缓解这个问题,我将setImageDrawable()
代码从displayPortrait()
移动到onCreate()
中。但还是没有改善。 - curioustechizenView
,然后使用graphics.drawBitmap()
直接将图像绘制到屏幕上。这可能会稍微复杂,因为我需要决定onMeasure()
的适当策略。我希望在赏金到期之前有所进展。 - curioustechizenView
和canvas.drawBitmap()
来创建电池图形。我将在原问题的答案中添加更多细节。 - curioustechizen