安卓:自定义视图在错误的x,y坐标处绘制

4
我正在尝试创建自定义视图,继承自视图组,并以定制的方式布局此视图组内的自定义子视图。基本上,我正在尝试创建类似于Outlook中的日历视图,其中每个事件的屏幕高度相对于其长度。

我在ViewGroup的构造函数中初始化了一个View的ArrayList,重写了onMeasure、onLayout和onDraw,一切都很正常,除了...渲染的视图都从(0,0)开始渲染,即使我将它们的左和右属性设置为其他值。它们的宽度和高度很好,只有它们的顶部和左边是错误的。

这是代码,我为了清晰简洁而缩写了它:

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);
    }
}

你是否记录了设置宽度的值? - Daniel Kobe
我调试并跟踪了所有本地变量。值已正确设置。此外,宽度不是问题 - 宽度和高度已正确设置。顶部值是问题所在:尽管它被设置为200、400、600等,但所有视图都在0处呈现。 - user884248
1个回答

2
由于某些原因,似乎使用ViewGroup绘制孩子的方式是将ViewGroup转换为子视图,然后在0,0处绘制子视图。
但事实证明,ViewGroup将处理所有子视图的绘制。如果您简化onDraw()方法,应该就可以了。
@Override
protected void onDraw(Canvas canvas) {
     // draw background grid
    mCalendarGridPainter.paint(canvas);
    // draw events
    super.onDraw(canvas);
}

现在我进一步查看了您的代码,我注意到您正在您的ViewGroup代码中填充子视图。最好在ViewGroup之外完成所有这些操作,使用addView()添加这些视图,然后在onLayout()期间使用getChildCount()getChildAt()访问子视图。

我假设我需要重写getView()和getViewCount()才能使这个工作? - user884248
我不这么认为。尝试使用我发布的onDraw()覆盖方法,也可以尝试不覆盖onDraw()方法,以查看默认行为。 - kris larson
哦,等一下,你没有在事件视图上调用CalendarDayViewGroup.addView()吗?更新我的答案... - kris larson
太棒了,它起作用了!非常感谢!我注释掉了绘制视图的那一行,在充气后调用了addView(),现在它可以工作了。如此奇怪。我不认为我在任何教程中看到过这个问题,包括developer.android.com。为什么在ViewGroup之外充气视图更好呢? - user884248
这只是一个更好的设计,因为你将容器视图与子视图解耦。以下是一个例子:一旦你的日历日期已经显示,你应该能够添加一个新的事件视图,并立即看到日历日期更新为新的事件视图。想象一下,如果你从某个服务器接收到有新事件可用的消息,那将会多么有用。教程很棒,但它们往往只是浅尝辄止。最终会有一个时刻,你必须深入了解并打开Android源代码,看看发生了什么。干杯! - kris larson
谢谢,这非常有帮助。 - user884248

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接