ImageView 方向改变 - Drawable 忽略 setBounds 并返回到原始状态
编辑3:
出于历史原因,我保留下面的原始问题。但是,我发现问题不仅仅局限于 FrameLayout
。因此,更新了标题。
我没有在这篇文章中添加更多代码,而是创建了一个示例项目来演示该问题;并上传到 Google 项目上主持。
该问题的摘要是这样的:
纵向方向的可绘制对象,其上设置了一定的边界 改变方向并返回纵向,不保留 设定界限。相反,它会恢复到原来的范围。这是在 尽管强制规定了方向变化的明确界限。 请注意,您稍后在 Click 等上设置的任何界限都会被遵守。
我还有 上传了该应用的一个版本,其中包含 2 个单独的活动来说明该问题。
- 普通的活动就说明了这个问题。
- 在
ImageView
上使用自定义BitmapDrawable
的活动 - 只要边界发生更改,此活动就会打印到日志。
第二个版本清楚地表明,即使我在 Drawable 上设置了边界,onBoundsChange()
中也不会遵守这些边界。
原始问题:
我正在使用一个 FrameLayout
,其中 2 个 ImageView
堆叠在一起,用于显示“电池状态”图形。这是纵向模式。在横向模式下,我显示不同的布局(图表)。
我的问题是这样的 - 假设电池状态显示 - 比如说 30%。现在,我旋转屏幕并显示图表。当我回到纵向方向时,电池图形返回到其原始位置(即“充满”)。
我尝试了各种方法来尝试弄清楚发生了什么。调试显示“顶部”图形的边界确实按预期设置。所以这似乎是一个无效问题。我提供了 2 个类和 2 个布局 XML(全部简化)的代码,这有助于重现问题。还附加用于 ImageView 的占位符 PNG。
有人能发现错误吗?要重现问题,请运行应用程序,然后单击“更新”按钮。图形将被“填充”到一定程度。然后,切换到横向,然后返回纵向。该图形不记得其先前的值。
Activity:
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()
中传入保存的值 - 基本上每当我们回到纵向模式时都会强制更新到已知状态。还是没有运气。请注意,日志告诉我可绘制对象的边界是正确的。我不明白为什么没有发生失效。
编辑2:
- 在ValueIndicator.java中,我引入了一个成员变量
mPercent
它始终存储最后已知的百分比值。 - 更新了
setPercent()
代码,更新了成员变量mPercent
;然后调用 updateIndicateUi() 方法。 updateIndicatorUi()
(现在是一个public
方法)现在使用状态(即mPercent
)来完成其工作。- 每当我们返回纵向模式时,我都会调用
updateIndicatorUi()
。这会强制电池图形自行更新。
我还更新了代码以反映这些更改。
这个想法是每当我们从横向模式返回到纵向模式时强制重绘和无效。再次 - 我确实看到电池“内容”可绘制的边界已根据需要设置,但用户界面拒绝跟上步伐。
EDIT 3:
I am maintaining the original question below for historical reasons. However, I have found that the issue is not isolated to FrameLayout
. Hence, updated the title.
Instead of over-crowding this post with more code, I have created a sample project which demonstrates the issue; and uploaded it on Google Project Hosting.
The summary of the issue is this:
A drawable in portrait orientation with a certain bounds set on it, on
changing orientation and returning to portrait, does not retain the
set bounds. Instead it reverts to its original bounds. This is in
spite of forcefully setting the explicit bounds on orientation change.
Do note that any bounds that you later set on Click etc are obeyed.
I have also uploaded a version of the app that contains 2 separate activities illustrating the issue.
- Plain vanilla activity that just illustrates the issue.
- Activity that uses a custom
BitmapDrawable
on theImageView
- this one prints to log whenever bounds are being changed.
The second version show clearly that even if I set the bounds on the Drawable, these bounds are not being obeyed in onBoundsChange()
.
Original Question:
I am using a FrameLayout
with 2 ImageView
s stacked one on top of the other for displaying a "Battery Status" graphic. This is in portrait mode. In Landscape mode, I display a different layout (a chart).
My issue is this - suppose the battery status is displaying - say 30%. Now, I rotate the screen and display the chart. When I come back to the Portrait orientation, the battery graphic goes back to its original position (which is "full").
I have tried all sorts of things to try and figure out what is happening. Debugging shows me that the bounds for the "top" graphic are indeed being set as expected. So this seems to be an invalidation issue. I am presenting the code for 2 classes and 2 layout XMLs (all simplified) which helps in reproducing the issue. Also attaching the placeholder PNGs used for the ImageViews.
Can anyone spot the error? To recreate the issue, run the app, and click in "Update" button. The graphic will be "filled" to a certain level. Then, switch to landscape and then back to Portrait. The graphic does not remember its earlier value.
The Activity:
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 (The XML for the FrameLayout used in the custom View)
<?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 (dummy placeholder - sufficient to recreate the issue)
<?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 snippet:
<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>
EDIT
I also tried calling setPercent()
and pass in a saved value in displayPortraitView()
- basically forcing an update to a known state whenever we get back to portrait mode. Still no luck. Do note that the logs tell me that the bounds of the drawables are correct. I can't figure out why the invalidation doesn't happen.
EDIT 2:
- In ValueIndicator.java, I introduced a member variable
mPercent
which always stores the last known percent value. - Updated the
setPercent()
code to update the member variablemPercent
; and then call theupdateIndicateUi()
method. updateIndicatorUi()
(which is now apublic
method) now uses the state (i.e,mPercent
) to do its job.- Whenever we are back to portrait mode, I call
updateIndicatorUi()
. This forces the battery graphic to update itself.
I also updated the code to reflect these changes.
The idea is to force a redraw and an invalidate whenever we return from landscape to portrait mode. Again - I do see the bounds of the battery "content" drawable being set as desired, but the UI refuses to keep pace.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我在 Google Code Hosting 上检查了您的代码(感谢您如此彻底地记录代码),我发现当您返回纵向时,Drawable 上设置的边界确实再次发生了更改从横向方向。
Drawable 的边界不是由您的代码更改的,而是由 ImageView 的布局方法更改的。当您放置新布局 (
setContentView
) 时,将运行所有布局代码,包括ImageView
的代码。ImageView
更改它所包含的可绘制对象的边界,这就是为什么您将可绘制对象的边界更改为原始边界的原因。导致边界更改的堆栈跟踪是:
当阅读您的代码时,我发现更改边界并存储边界等只是为了绘制一个仪表而太过分了。我可以建议以下之一:
View
的类并重写onDraw(Canvas)
,然后使用drawRect
绘制红色矩形。I've examined your code on Google Code Hosting (appreciate your effort to document the code so thoroughfully), and I found that the bounds set on the Drawable is indeed changed again when you go back to the portrait from landscape orientation.
The bounds on the Drawable is changed not by your code, but by
ImageView
's layout method. When you place a new layout (setContentView
), all layoutting code is run, includingImageView
's.ImageView
changes the bound of the drawable that it contains, that's why you get the bounds of the drawable changed to the original one.The stack trace leading to the bound change is:
When reading your code I found it overkill to change bounds and storing the bounds etc. just to draw a meter. May I suggest one of the following:
ImageView
itself (usingsetLayoutParams
) instead of the bounds of its drawable.View
and overrideonDraw(Canvas)
and then draw the red rectangle usingdrawRect
.只是猜测,但在 RotateActivity 中,尝试将 super 调用的顺序更改为:
Just a guess, but in RotateActivity, try changing the order of the super call to this:
我通过使用一个好的旧自定义
View
对象解决了这个问题。在onDraw()
中,我将位图绘制到Canvas
上,并根据电池百分比调整边界大小。上传了工作项目的存档 此处位于 Google 项目托管。 这个答案帮助了我,因此我接受并授予了赏金。不过,我仍然不相信为此使用自定义
View
的必要性。 IMO,我应该能够将ImageView
放置在Layout
中,并且这应该仍然有效。我只是想不出一种方法来告诉布局不要打扰我的Drawable
!此外,该电池图形最初旨在与其他图形(温度、信号强度...)一起在 2 列表布局中使用。因此,该解决方案对这些情况的适用性还有待观察。
另外,我的解决方案有一个小错误 - 电池第一次总是显示为空。当我找到解决方案时,我会将解决方案上传到同一个项目。
I solved this problem by using a good old custom
View
object. InonDraw()
, I draw the bitmaps toCanvas
, and resize the bounds according to battery percent. Uploaded the archive for the working project here at Google Project Hosting. This answer helped me on my way and hence I have accepted and awarded the bounty.I still am not convinced about the need for using a custom
View
for this though. IMO, I should be able to place theImageView
in aLayout
and this should still work. I just can't figure out a way to tell the layout to leave myDrawable
s alone!Further, this battery graphic was originally intended to be used along with other graphics (temperature, signal strength ... ) in a 2-column table layout. So the applicability of this solution to those cases remains to be seen.
Also, my solution has a minor bug - the battery always appears empty the first time. I will upload the solution to the same project when I find the solution for this.