Android:自定义视图在错误的 x、y 坐标处绘制
Android: custom views get drawn at wrong x,y coordinates
我正在尝试创建一个自定义视图,从视图组继承,并以自定义方式在该视图组内布局自定义子视图。基本上我正在尝试创建一个类似于 outlook 中的日历视图,其中每个事件都占据相对于其长度的屏幕高度。
我在 ViewGroup 的构造函数中初始化了一个 ArrayList of View,覆盖了 onMeasure、onLayout 和 onDraw,一切正常,除了...渲染的视图都从 (0,0) 开始渲染,即使我设置了它们left 和 right 属性为其他值。他们的宽度和高度没问题,只有他们的顶部和左边是错误的。
这是代码,为了清晰和简单起见,我对其进行了缩写:
public class CalendarDayViewGroup extends ViewGroup {
private Context mContext;
private int mScreenWidth = 0;
private ArrayList<Event> mEvents;
private ArrayList<View> mEventViews;
// CalendarGridPainter is a class that draws the background grid.
// this one works fine so I didn't write its actual code here.
// it just takes a Canvas and draws lines on it.
// I also tried commenting out this class and got the same result,
// so this is DEFINITELY not the problem.
private CalendarGridPainter mCalendarGridPainter;
public CalendarDayViewGroup(Context context, Date date) {
super(context);
init(date, context);
}
//... other viewGroup constructors go here...
private void init(Date date, Context context) {
mContext = context;
// the following line loads events from a database
mEvents = AppointmentsRepository.getByDateRange(date, date);
// inflate all event views
mEventViews = new ArrayList<>();
LayoutInflater inflater = LayoutInflater.from(mContext);
for (int i = 0; i < mEvents.size(); i++) {
View view = getSingleEventView(mEvents.get(i), inflater);
mEventViews.add(view);
}
// set this flag so that the onDraw event is called
this.setWillNotDraw(false);
}
private View getSingleEventView(Event event, LayoutInflater inflater) {
View view = inflater.inflate(R.layout.single_event_view, null);
// [set some properties in the view's sub-views]
return view;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
// get screen width and create a new GridPainter if needed
int screenWidth = MeasureSpec.getSize(widthMeasureSpec);
if (mScreenWidth != screenWidth)
{
mScreenWidth = screenWidth;
mCalendarGridPainter = new CalendarGridPainter(screenWidth);
}
int numChildren = mEvents.size();
for (int i = 0; i < numChildren; i++) {
View child = mEventViews.get(i);
Event event = mEvents.get(i);
// event width is the same as screen width
int specWidth = MeasureSpec.makeMeasureSpec(mScreenWidth, MeasureSpec.EXACTLY);
// event height is calculated by its length, the calculation was ommited here for simplicity
int eventHeight = 350; // actual calculation goes here...
int specHeight = MeasureSpec.makeMeasureSpec(eventHeight, MeasureSpec.EXACTLY);
child.measure(specWidth, specHeight);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int numChildren = mEvents.size();
for (int i = 0; i < numChildren; i++) {
View child = mEventViews.get(i);
Event event = mEvents.get(i);
int eventLeft = 0;
int eventTop = (i + 1) * 200; // test code, make each event start 200 pixels after the previous one
int eventWidth = eventLeft + child.getMeasuredWidth();
int eventHeight = eventTop + child.getMeasuredHeight();
child.layout(eventLeft, eventTop, eventWidth, eventHeight);
}
}
@Override
protected void onDraw(Canvas canvas) {
// draw background grid
mCalendarGridPainter.paint(canvas);
// draw events
for (View view : mEventViews) {
view.draw(canvas);
}
super.onDraw(canvas);
}
}
出于某种原因,似乎 children 与 ViewGroup
s 的绘制方式是 ViewGroup
将 canvas 转换为 child的位置然后在 0,0 绘制 child。
但事实证明,ViewGroup
会为您处理children的所有绘图。我想如果你简化你的 onDraw()
方法你应该准备就绪:
@Override
protected void onDraw(Canvas canvas) {
// draw background grid
mCalendarGridPainter.paint(canvas);
// draw events
super.onDraw(canvas);
}
现在我正在进一步查看您的代码,我注意到您在 ViewGroup 的代码中夸大了 child 视图。最好在 ViewGroup 之外执行所有这些操作,使用 addView()
添加这些视图,然后使用 getChildCount()
和 getChildAt()
在 onLayout()
期间访问 child 视图.
我正在尝试创建一个自定义视图,从视图组继承,并以自定义方式在该视图组内布局自定义子视图。基本上我正在尝试创建一个类似于 outlook 中的日历视图,其中每个事件都占据相对于其长度的屏幕高度。
我在 ViewGroup 的构造函数中初始化了一个 ArrayList of View,覆盖了 onMeasure、onLayout 和 onDraw,一切正常,除了...渲染的视图都从 (0,0) 开始渲染,即使我设置了它们left 和 right 属性为其他值。他们的宽度和高度没问题,只有他们的顶部和左边是错误的。
这是代码,为了清晰和简单起见,我对其进行了缩写:
public class CalendarDayViewGroup extends ViewGroup {
private Context mContext;
private int mScreenWidth = 0;
private ArrayList<Event> mEvents;
private ArrayList<View> mEventViews;
// CalendarGridPainter is a class that draws the background grid.
// this one works fine so I didn't write its actual code here.
// it just takes a Canvas and draws lines on it.
// I also tried commenting out this class and got the same result,
// so this is DEFINITELY not the problem.
private CalendarGridPainter mCalendarGridPainter;
public CalendarDayViewGroup(Context context, Date date) {
super(context);
init(date, context);
}
//... other viewGroup constructors go here...
private void init(Date date, Context context) {
mContext = context;
// the following line loads events from a database
mEvents = AppointmentsRepository.getByDateRange(date, date);
// inflate all event views
mEventViews = new ArrayList<>();
LayoutInflater inflater = LayoutInflater.from(mContext);
for (int i = 0; i < mEvents.size(); i++) {
View view = getSingleEventView(mEvents.get(i), inflater);
mEventViews.add(view);
}
// set this flag so that the onDraw event is called
this.setWillNotDraw(false);
}
private View getSingleEventView(Event event, LayoutInflater inflater) {
View view = inflater.inflate(R.layout.single_event_view, null);
// [set some properties in the view's sub-views]
return view;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
// get screen width and create a new GridPainter if needed
int screenWidth = MeasureSpec.getSize(widthMeasureSpec);
if (mScreenWidth != screenWidth)
{
mScreenWidth = screenWidth;
mCalendarGridPainter = new CalendarGridPainter(screenWidth);
}
int numChildren = mEvents.size();
for (int i = 0; i < numChildren; i++) {
View child = mEventViews.get(i);
Event event = mEvents.get(i);
// event width is the same as screen width
int specWidth = MeasureSpec.makeMeasureSpec(mScreenWidth, MeasureSpec.EXACTLY);
// event height is calculated by its length, the calculation was ommited here for simplicity
int eventHeight = 350; // actual calculation goes here...
int specHeight = MeasureSpec.makeMeasureSpec(eventHeight, MeasureSpec.EXACTLY);
child.measure(specWidth, specHeight);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int numChildren = mEvents.size();
for (int i = 0; i < numChildren; i++) {
View child = mEventViews.get(i);
Event event = mEvents.get(i);
int eventLeft = 0;
int eventTop = (i + 1) * 200; // test code, make each event start 200 pixels after the previous one
int eventWidth = eventLeft + child.getMeasuredWidth();
int eventHeight = eventTop + child.getMeasuredHeight();
child.layout(eventLeft, eventTop, eventWidth, eventHeight);
}
}
@Override
protected void onDraw(Canvas canvas) {
// draw background grid
mCalendarGridPainter.paint(canvas);
// draw events
for (View view : mEventViews) {
view.draw(canvas);
}
super.onDraw(canvas);
}
}
出于某种原因,似乎 children 与 ViewGroup
s 的绘制方式是 ViewGroup
将 canvas 转换为 child的位置然后在 0,0 绘制 child。
但事实证明,ViewGroup
会为您处理children的所有绘图。我想如果你简化你的 onDraw()
方法你应该准备就绪:
@Override
protected void onDraw(Canvas canvas) {
// draw background grid
mCalendarGridPainter.paint(canvas);
// draw events
super.onDraw(canvas);
}
现在我正在进一步查看您的代码,我注意到您在 ViewGroup 的代码中夸大了 child 视图。最好在 ViewGroup 之外执行所有这些操作,使用 addView()
添加这些视图,然后使用 getChildCount()
和 getChildAt()
在 onLayout()
期间访问 child 视图.