Android 未绘制FrameLayout中的子对象
我在我的Android应用程序中实现了一个功能,用户可以在Android 未绘制FrameLayout中的子对象,android,layout,invalidation,android-framelayout,Android,Layout,Invalidation,Android Framelayout,我在我的Android应用程序中实现了一个功能,用户可以在FrameLayout中动态添加和删除视图,并使用拖放技术以绝对坐标定位视图。用户通常会将楼层规划图像作为背景加载到框架布局,然后添加表示室内灯具的按钮,并在适当的位置对其进行定位 无论如何,我有一个问题,当片段加载时,我需要充气并将视图添加到框架布局。子视图来自SQLite数据库中的信息。为了正确定位子视图(取决于方向),我需要考虑FrameLayout的宽度和高度。因此,我无法在我的片段中的onCreateView()中执行此工作 F
FrameLayout
中动态添加和删除视图,并使用拖放技术以绝对坐标定位视图。用户通常会将楼层规划图像作为背景加载到框架布局
,然后添加表示室内灯具的按钮,并在适当的位置对其进行定位
无论如何,我有一个问题,当片段
加载时,我需要充气并将视图添加到框架布局
。子视图来自SQLite数据库中的信息。为了正确定位子视图(取决于方向),我需要考虑FrameLayout
的宽度和高度。因此,我无法在我的片段中的onCreateView()
中执行此工作
FrameLayout
实际上是一个DragArea
(为了简单起见,我仍然将这个对象称为FrameLayout
),一个扩展FrameLayout
的自定义类。为了在计算宽度/高度时获得对片段的回调,我在FrameLayout
中覆盖onSizeChanged
,它调用片段
奇怪的是。如果我在我的片段
中的回调方法中创建视图并调用FrameLayout.invalidate
似乎什么都没有发生。检查显示视图已添加到FrameLayout
,但未显示。如果打开hierarchyviewer并按Load View Hierarchy,将显示视图。因此,这里似乎有一个副作用,吸引了人们的观点。我尝试调用FrameLayout.requestLayout()
,并通过执行getActivity().runOnUiThread()
中的逻辑来确保它在UI线程上运行,但这没有什么区别
但是如果我将逻辑放在一个(否则无用的)AsyncTask
中的onPostExecuted()
中,则会绘制视图
请参阅下面的代码,注释中对此进行了进一步说明。回调方法名为onWidthAvailable()
,并从FrameLayout.onSizeChanged()
调用
我还尝试在以后的任意时间点调用了invalidate()
,但没有成功
public class ImageFragment extends Fragment implements FragmentRedrawable, OnLongClickListener, OnWidthAvailableListener {
...
/*
* This will, among other things, inflate mPlanningView (A DragArea which extends FrameLayout). This is the FrameLayout that will contain the childs.
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final LinearLayout view = (LinearLayout) inflater.inflate(R.layout.fragment_image, null);
final ImageView image = (ImageView) view.findViewById(R.id.house_planning);
image.setImageResource(R.drawable.houseplanning);
mPlanningView = (com.doffman.dragarea.DragArea) view.findViewById(R.id.imageview);
image.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mPlanningView.invalidate();
setTouchPoint((int) event.getX(), (int) event.getY());
Log.d(Constants.TAG, String.format("Point (%f, %f)", event.getX(), event.getY()));
}
return false;
}
});
image.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
Log.d(Constants.TAG, "Show context menu for point: " + mTouchPoint.x + ", " + mTouchPoint.y);
MenuInflater inflater = new MenuInflater(getActivity());
inflater.inflate(R.menu.image_context_menu, menu);
}
});
mPlanningView.addDragListener(mPlanningView, new OnDragListener() {
@Override
public void onDrag(View view, final DragEvent dragEvent) {
switch (dragEvent.getAction()) {
case com.doffman.dragarea.DragEvent.ACTION_DRAG_STARTED:
mActivity.disallowViewPagerInterception(true);
break;
case com.doffman.dragarea.DragEvent.ACTION_DROP:
final Bundle data = dragEvent.getBundle();
final String tag = data.getString("tagImageItem");
final Integer itemType = data.getInt("tagItemType");
final Integer itemId = data.getInt("tagItemId");
final View v = mPlanningView.findViewWithTag(tag);
if (v == null)
return;
animate(v).setDuration(0).x(dragEvent.getX()).y(dragEvent.getY());
mDb.setImageItemCoordinates(1, itemType, itemId, dragEvent.getX(), dragEvent.getY());
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
moveView(v, dragEvent.getX(), dragEvent.getY());
}
});
break;
case com.doffman.dragarea.DragEvent.ACTION_DRAG_ENDED:
mActivity.disallowViewPagerInterception(false);
break;
default:
break;
}
}
});
mPlanningView.getLocationOnScreen(mPlanningViewCoordinates);
Log.d(Constants.TAG, "onCreateView: Width: " + mPlanningView.getWidth());
mPlanningView.setOnWidthAvailableListener(this);
return view;
}
/*
* This is called from mPlanningView.onSizeChanged() (i.e. when the width of
* mPlanningView has been calculated)
*
* This code will read information from SQLite and create Views that will be placed using
* coordinates defined in the database.
*/
@Override
public void onWidthAvailable() {
Log.d(Constants.TAG, "onWidthAvailable: Width is " + mPlanningView.getWidth());
if (mPlanningView.getWidth() == 0)
return;
mPlanningView.removeOnWidthAvailableListener();
/* The most natural thing would be to just do the logic directly. This will indeed add the
* views to mPlanningView but they are never drawn/seen.
Log.d(Constants.TAG, "onWidthAvailable thread: " + Thread.currentThread().getId());
List<ImageLayoutItem> items = mDb.getItemsForImagePage(1);
for (final ImageLayoutItem item : items) {
placeEntity(item);
}
*/
/* But if the logic is instead placed in an AsyncTask, the views are drawn */
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
return null;
}
@Override
protected void onPostExecute(Void result) {
Log.d(Constants.TAG, "onPostExecute: UI thread: " + Thread.currentThread().getId());
List<ImageLayoutItem> items = mDb.getItemsForImagePage(1);
for (final ImageLayoutItem item : items) {
placeEntity(item);
}
}
}.execute();
}
private void moveView(View v, float xFactor, float yFactor) {
int xy[] = translateToAbsoluteCoordinates(xFactor, yFactor);
animate(v).setDuration(0).x(xy[0]).y(xy[1]);
FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) v.getLayoutParams();
p.leftMargin = xy[0];
p.topMargin = xy[1];
p.gravity = Gravity.TOP;
v.setLayoutParams(p);
mPlanningView.invalidate();
}
private int[] translateToAbsoluteCoordinates(float xFactor, float yFactor) {
int xy[] = new int[2];
float width = (float) mPlanningView.getWidth();
float relFloat = width * xFactor;
int relativeX = (int) (relFloat);
int relativeY = (int) ((float) mPlanningView.getHeight() * yFactor);
xy[0] = mPlanningViewCoordinates[0] + relativeX;
xy[1] = mPlanningViewCoordinates[1] + relativeY;
return xy;
}
public void placeEntity(ImageLayoutItem item) {
LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
View itemLayout = inflater.inflate(item.getEntity().getItemLayoutId(prefs), null);
itemLayout.setTag(item.getEntity().getItemTypeId() + ":" + item.getEntity().getId());
itemLayout.setTag(R.id.tagItemType, item.getEntity().getItemTypeId());
itemLayout.setTag(R.id.tagItemId, item.getEntity().getId());
item.getEntity().setView(getActivity(), prefs, mProgressViewer, itemLayout, this);
if (!item.isShowTitle()) {
View entityName = itemLayout.findViewById(R.id.entityName);
if (entityName != null)
entityName.setVisibility(View.GONE);
}
if (item.isUseBackground()) {
itemLayout.setBackgroundColor(item.getBackgroundColor());
}
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
itemLayout.setLayoutParams(params);
mPlanningView.addView(itemLayout, params);
moveView(itemLayout, item.getXFactor(), item.getYFactor());
itemLayout.setOnLongClickListener(this);
}
...
}
公共类ImageFragment扩展了Fragment实现了FragmentRedrawable、OnLongClickListener、OnWidthAvailableListener{
...
/*
*这将使mPlanningView(一种扩展FrameLayout的DragArea)膨胀,这是包含child的FrameLayout。
*/
@凌驾
CreateView上的公共视图(布局、充气机、视图组容器、捆绑包保存状态){
最终线性布局视图=(线性布局)充气器充气(R.layout.fragment_图像,空);
最终ImageView图像=(ImageView)视图。findViewById(R.id.house_planning);
image.setImageResource(R.drawable.houseplanning);
mPlanningView=(com.doffman.dragarea.dragarea)view.findviewbyd(R.id.imageview);
setOnTouchListener(新的OnTouchListener(){
@凌驾
公共布尔onTouch(视图v,运动事件){
if(event.getAction()==MotionEvent.ACTION\u向下){
mPlanningView.invalidate();
setTouchPoint((int)event.getX(),(int)event.getY());
Log.d(Constants.TAG,String.format(“点(%f,%f)”,event.getX(),event.getY());
}
返回false;
}
});
setOnCreateContextMenuListener(新的OnCreateContextMenuListener(){
@凌驾
public void onCreateContextMenu(ContextMenu菜单、视图v、ContextMenuInfo菜单信息){
Log.d(Constants.TAG,“显示点的上下文菜单:“+mTouchPoint.x+”,“+mTouchPoint.y”);
MenuInflater充气器=新的MenuInflater(getActivity());
充气器。充气(右菜单。图像上下文菜单,菜单);
}
});
addDragListener(mPlanningView,新的OnDragListener()){
@凌驾
公共排水孔(视图,最终排水孔排水孔){
开关(dragEvent.getAction()){
案例com.doffman.dragarea.DragEvent.ACTION\u DRAG\u已启动:
mActivity.disallowViewPagerInterception(true);
打破
案例com.doffman.dragarea.DragEvent.ACTION\u DROP:
final Bundle data=dragEvent.getBundle();
最终字符串标记=data.getString(“tagImageItem”);
最终整数itemType=data.getInt(“tagItemType”);
最终整数itemId=data.getInt(“tagItemId”);
最终视图v=mPlanningView.findViewWithTag(标记);
如果(v==null)
回来
设置动画(v).setDuration(0).x(dragEvent.getX()).y(dragEvent.getY());
setImageItemCoordinates(1,itemType,itemId,dragEvent.getX(),dragEvent.getY());
getActivity().runOnUiThread(新的Runnable()){
@凌驾
公开募捐{
moveView(v,dragEvent.getX(),dragEvent.getY());
}
});
打破
案例com.doffman.dragarea.DragEvent.ACTION\u DRAG\u结束:
mActivity.disallowViewPagerInterception(false);
打破
违约:
打破
}
}
});
mPlanningView.getLocationOnScreen(mPlanningViewCoordinates);
d(Constants.TAG,“onCreateView:Width:+mPlanningView.getWidth());
mPlanningView.setOnWidthAvailableListener(此);
返回视图;
}
/*
*这是从mPlanningView.onSizeChanged()调用的(即当
*mPlanningView(已计算)
*
*此代码将从SQLite读取信息,并创建使用
*定义的坐标i