简体   繁体   English

Android:设计日历应用程序的视图

[英]Android: designing the view for a calendar app

I'm writing a calendar application for Android. 我正在为Android编写日历应用程序。 The calendar needs to a have a day display similar to the default application, or MS outlook: a grid showing a line for each hour, and the appointments shown as rectangles. 日历需要具有类似于默认应用程序或MS Outlook的日显示:网格显示每小时的行,约会显示为矩形。

Here's a similar sample image from Google Images: 这是来自Google图片的类似示例图片:

在此处输入图片说明

I downloaded the source code for the calendar app from Google's Android Open Source Project, and saw that they implemented this display as a custom view which simplay uses Canvas.drawRect() to draw the rectangles, and then they implemented their own hit-test when the user clicks, to see which appointment was clicked. 我从Google的Android开放源代码项目中下载了日历应用程序的源代码,并看到他们将该显示实现为自定义视图,simplay使用Canvas.drawRect()绘制矩形,然后在实现时执行了自己的点击测试用户单击以查看单击了哪个约会。

I already wrote some of that stuff on my own and it works great, and isn't too complicated. 我已经自己写了一些东西,效果很好,并且不太复杂。

The problem is that I need the different lines of text inside the rectangles (the subject, the time) to be links to various functionality, and I'm wondering how I can do that. 问题是我需要矩形内的不同文本行(主题,时间)链接到各种功能,我想知道如何做到这一点。

When I draw, I already create Rects for each appointment. 绘画时,我已经为每个约会创建了Rect。 I was thinking I could create Rects for each piece of text as well, cache all of these in an ArrayList, and then perform the histtest against the cached rects. 我当时想我也可以为每个文本创建Rect,将所有这些都缓存在ArrayList中,然后对缓存的rect执行histt​​est。 I'm only afraid this whole thing will be too heavy... does this sound like a solid design? 我只是担心这整个事情会太重...听起来像是坚固的设计吗?

Or should I avoid the custom drawing altogether and programmatically generate and place views (TextViews maybe?) I'm an Android novice and I'm not sure what my options are... 还是应该完全避免使用自定义图形,而是以编程方式生成和放置视图(也许是TextViews?),我是Android的新手,我不确定我的选择是什么...

Thanks for helping out! 感谢您的帮助!

Alright, as announced, here some example: 好了,正如所宣布的,这里有一些例子:

If you just use a custom view, you have to keep lists of objects and draw them yourself, as opposed to a custom layout where you just have to measure and layout the children. 如果仅使用自定义视图,则必须保留对象列表并自己绘制它们,而自定义布局则只需要测量和布置子代即可。 Since you can just add a button, there's no need to use hit-tests or whatsoever, since if you don't mess up the view will just receive the onClick() call. 由于您仅可以添加一个按钮,因此无需使用点击测试或其他任何测试,因为如果您不搞砸,视图将只收到onClick()调用。

Also, you can easily preview your layout in the editor if you correctly implement layout parameters. 另外,如果正确实现布局参数,则可以在编辑器中轻松预览布局。 Which makes development much faster. 这使开发速度大大提高。

Eg you can define your own layout parameters 例如,您可以定义自己的布局参数

<resources>
    <declare-styleable name="TimeLineLayout_Layout">
        <attr name="time_from" format="string"/>
        <attr name="time_to" format="string"/>
    </declare-styleable>
</resources>

Then use them like this... 然后像这样使用它们...

<com.github.bleeding182.timelinelayout.TimeLineLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#22662222">

    <TextView
        android:layout_width="80dp"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_green_dark"
        android:padding="8dp"
        android:text="12:00 - 16:00"
        app:time_from="12:00"
        app:time_to="16:00"/>

</com.github.bleeding182.timelinelayout.TimeLineLayout>

And the result would look something like this (I know it's ugly, but I made this just for testing :/ ) 结果看起来像这样(我知道这很丑陋,但是我只是为了测试:/)

日历检视

To do this, you create a basic layout where you measure and layout the views. 为此,您将创建一个基本布局,在其中测量和布局视图。 You can then add any views to your layout, and by setting a time from / to and correctly measuring / layouting you can easily display all sorts of items. 然后,您可以在布局中添加任何视图,并且通过设置/到/之间的时间并正确地测量/布局,可以轻松地显示各种项目。

The code for the screenshot is attached below, onDraw will create those ugly hour/half hour lines. 屏幕截图的代码附在下面, onDraw将创建这些丑陋的小时/半小时行。 onMeasure is for calculating view heights and onLayout is drawing the views to their correct time slot. onMeasure用于计算视图高度,而onLayout用于将视图绘制到正确的时隙。

I hope this helps, it's sure easier to use than handling everything in one view. 我希望这会有所帮助,它肯定比在一个视图中处理所有内容容易使用。

public class TimeLineLayout extends ViewGroup {

    private int tIntervalSpan = 24 * 60;
    private float mMeasuredMinuteHeight;

    public TimeLineLayout(Context context) {
        super(context);
    }

    public TimeLineLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TimeLineLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public TimeLineLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i = 0; i < getChildCount(); i++) {
            final View child = getChildAt(i);
            ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
            if (layoutParams instanceof LayoutParams) {
                LayoutParams params = (LayoutParams) layoutParams;
                final int top = (int) (params.tFrom * mMeasuredMinuteHeight);
                child.layout(l, top, child.getMeasuredWidth(), top + child.getMeasuredHeight());
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));

        mMeasuredMinuteHeight = getMeasuredHeight() / (float) tIntervalSpan;

        for (int i = 0; i < getChildCount(); i++) {
            final View child = getChildAt(i);
            ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
            if (layoutParams instanceof LayoutParams) {
                LayoutParams params = (LayoutParams) layoutParams;
                child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec((int) ((params.tTo - params.tFrom) * mMeasuredMinuteHeight), MeasureSpec.EXACTLY));
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        final float height = mMeasuredMinuteHeight * 60;
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        for(int i = 0; i < 24; i++) {
            paint.setStrokeWidth(2f);
            paint.setAlpha(255);
            canvas.drawLine(0, i * height, getMeasuredWidth(), i*height, paint);
            if(i < 23) {
                paint.setStrokeWidth(1f);
                paint.setAlpha(50);
                canvas.drawLine(0, i * height + 30 * mMeasuredMinuteHeight, getMeasuredWidth(), i * height + 30 * mMeasuredMinuteHeight, paint);
            }
        }
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    @Override
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    public static class LayoutParams extends ViewGroup.LayoutParams {

        private final int tFrom;
        private final int tTo;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.TimeLineLayout_Layout);
            final String from = a.getString(R.styleable.TimeLineLayout_Layout_time_from);
            final String to = a.getString(R.styleable.TimeLineLayout_Layout_time_to);
            a.recycle();
            tFrom = Integer.parseInt(from.split(":")[0]) * 60 + Integer.parseInt(from.split(":")[1]);
            tTo = Integer.parseInt(to.split(":")[0]) * 60 + Integer.parseInt(to.split(":")[1]);
        }
  }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM