条形时钟






4.85/5 (22投票s)
将时间显示为一系列(
引言
尽管它们已经存在很长时间了,但前几天我正在看一个模拟时钟,注意到一些我以前从未留意过的事情。在一个有三个指针(即时针、分针、秒针)的时钟上,当秒针绕着表盘转动时,分针会极其微小地移动,而不是从一分钟跳到下一分钟。同样,时针也是如此,尽管慢得多。所以,例如,如果秒针在 :30,分针就会在两分钟之间的一半位置。如果分针在 :15,时针就会在两小时之间 1/4 的位置。嗯……
背景
虽然这是我在 CodeProject 的第一篇 Android 文章,但这并不是我创建的第一个应用程序。我创建的应用程序数量大概有 40 多个,其中两个已经发布,第三个也差不多快要发布了。然而,这是我创建的第一个应用程序,其中我必须添加代码来“绘制”一些 UI 控件,而不是让框架为我完成绘图。因此,本文将简要演示如何扩展 ProgressBar
类。
使用代码
我希望我的进度条扩展能够显示其值作为文本,可以显示在进度条上或进度条下方。鉴于这个要求,新类的名称就变成了
public class TextProgressBar extends ProgressBar
在不覆盖任何内容的情况下,可以在布局文件中这样引用该类
<com.dcrow.BarClock.TextProgressBar android:id="@+id/progHour" android:layout_height="64dp" android:layout_marginbottom="25dp" android:layout_width="match_parent" android:max="23">
此时,进度条将正常运行。由于我需要一些自定义,因此需要覆盖 onDraw()
方法。实际的绘图将在这里进行。调用超类 onDraw()
将负责绘制实际的进度条,而所有其他内容(例如刻度线、文本)都可以在之后绘制。
我首先想要的是一条水平线,位于进度条的中间,并贯穿进度条的整个长度
// draw horizontal line the width of the progress bar
int nMiddle = getHeight() / 2;
canvas.drawLine(0, nMiddle, getWidth(), nMiddle, m_paintLine);
接下来,我想要的是在先前绘制的水平线上有一系列垂直刻度线。根据进度条的最大值,刻度线的数量和它们之间的间隔会有所不同。
// what is the distance between each tick mark?
float increment = (float) getWidth() / getMax();
// draw the tick marks
for (int i = 0; i <= getMax(); i++)
{
float x = i * increment;
canvas.drawLine(x, nMiddle - 20.0f, x, nMiddle + 20.0f, m_paintLine);
}
看起来不错,但 **分针** 和 **秒针** 进度条上的刻度线有点难以辨认。对于这两个,解决方案是使每 5 个刻度线变大。模拟时钟表盘也符合这一点,因为数字 1-12 相隔 5 秒。我们只需要检查循环控制变量 i
的值,看看它是否能被 5 整除,比如
if (i % 5 == 0)
canvas.drawLine(x, nMiddle - 30.0f, x, nMiddle + 30.0f, m_paintLine);
else
canvas.drawLine(x, nMiddle - 20.0f, x, nMiddle + 20.0f, m_paintLine);
现在刻度线已经就位,是时候处理文本了(即进度条的值)。我希望它在进度线上居中,并随着进度从左到右移动。在最左边和最右边,我不希望它消失。
// what percent are we done? float fPercent = ((float) getProgress() / getMax()) * getWidth(); // get the width of the text to be drawn m_paintText.getTextBounds(m_text, 0, m_text.length(), m_bounds); float pos = fPercent; // tentative X position if (pos < m_bounds.width() / 2.0f) { m_paintText.setTextAlign(Align.LEFT); pos = 0.0f; // don't go past left edge } else if (pos > getWidth() - (m_bounds.width() / 2.0f)) { m_paintText.setTextAlign(Align.RIGHT); pos = getWidth(); // don't go past right edge } else m_paintText.setTextAlign(Align.CENTER); // draw the 'percent' text up from the bottom edge canvas.drawText(m_text, pos, getHeight() - 10.0f, m_paintText);
有了绘制文本的代码后,进度条现在看起来像
控件本身(大部分)完成后,我需要响应时间变化并相应地更新进度条。我设置了一个 1 秒间隔的计时器,当时间改变时,将执行以下代码
Calendar now = Calendar.getInstance(); int nHour = now.get(Calendar.HOUR_OF_DAY); int nMinute = now.get(Calendar.MINUTE); int nSecond = now.get(Calendar.SECOND); m_progHour.setProgress(nHour); m_progMinute.setProgress(nMinute); m_progSecond.setProgress(nSecond);
关注点
最初,主布局看起来与以下内容类似
<linearlayout android:layout_height="match_parent" android:layout_width="match_parent" android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" > <textview android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="@string/hour" android:textsize="20sp"> <com.dcrow.barclock.textprogressbar android:id="@+id/progHour" android:layout_height="64dp" android:layout_width="match_parent" android:max="23"> <textview android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="@string/minute" android:textsize="20sp"> <com.dcrow.barclock.textprogressbar android:id="@+id/progMinute" android:layout_height="64dp" android:layout_width="match_parent" android:max="59"> <textview android:layout_height="wrap_content" android:layout_width="wrap_content" android:text="@string/second" android:textsize="20sp"> <com.dcrow.barclock.textprogressbar android:id="@+id/progSecond" android:layout_height="64dp" android:layout_width="match_parent" android:max="59"> </LinearLayout>
这按预期工作,但它没有给 **时针** 和 **分针** 进度条提供足够的粒度。每个条的进度作为一个整体单位前进,而不是作为一个整体的百分比。为了纠正这个问题,我只需要将每个条的 max
值分别更改为 86399(一天的 0 基秒数)和 3599(一小时的 0 基秒数)。现在,由于每个条都基于总秒数,我需要更改发送到每个条的 setProgress()
方法的值,如下所示
m_progHour.setProgress((nHour * 3600) + (nMinute * 60) + nSecond); m_progMinute.setProgress((nMinute * 60) + nSecond); m_progSecond.setProgress(nSecond);
最终结果
请注意,由于 **秒针** 进度条显示 32 秒,**分针** 进度条在两个分钟间隔之间大约处于 1/2 的位置。同样,由于 **分针** 进度条显示 49 分钟,**时针** 进度条在两个小时间隔之间略高于 3/4 的位置。这就是我想要创建的外观。
颜色
在创建此应用程序的过程中,我一部分时间在想,进度条及其背景的渐变颜色足以使其“不那么无聊”。我找到的第一个解决方案涉及在布局文件中使用进度条的 android:progressDrawable
属性。将其分配给可绘制文件夹中的以下文件即可完成此操作
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@android:id/background"> <shape> <corners android:radius="3dp" /> <gradient android:startColor="#f0f0f0" android:centerY="1.0" android:endColor="#b9b9b9" android:angle="270" /> </shape> </item> <item android:id="@android:id/progress"> <clip> <shape> <corners android:radius="3dp" /> <gradient android:startColor="#93d1ea" android:centerY="1.0" android:endColor="#35aad7" android:angle="270" /> </shape> </clip> </item> </layer-list>
有了这些颜色,进度条就有了蓝色渐变,背景则是灰色渐变,两者都从上到下。看了一会儿之后,我认为最好还是让颜色可自定义。于是我朝着那个方向努力……
我需要找到一种方法,在运行时获取这个可绘制对象,修改它,然后将其放回原位。在尝试了所有我能找到的方法几天之后,一无所获。无论我放什么代码,android:progressDrawable
属性总是优先。然后我决定放弃使用该属性,而是完全通过代码来完成。
从进度条的 setProgressDrawable()
方法向后追溯,我发现有几个可绘制对象需要创建并连接在一起才能使其工作。因为进度条实际上使用三个可绘制对象(即背景、次级进度、进度)相互堆叠来实现其效果,所以我需要向 setProgressDrawable()
发送一个 LayerDrawable
对象。该对象将包含其他可绘制对象。
用作背景的可绘制对象看起来像
GradientDrawable gdBackground = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, new int[] {colors[0], colors[1]}); gdBackground.setShape(GradientDrawable.RECTANGLE); gdBackground.setCornerRadius(7.0f);
用作次级进度和(主)进度可绘制对象看起来像
GradientDrawable gdProgress = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, new int[] {colors[2], colors[3]}); gdProgress.setShape(GradientDrawable.RECTANGLE); gdProgress.setCornerRadius(7.0f); // just enough round to take the sharpness off ClipDrawable cdProgress = new ClipDrawable(gdProgress, Gravity.LEFT, ClipDrawable.HORIZONTAL);
将它们放入数组,并让 LayerDrawable
对象知道顺序看起来像
// since we do not have a secondary progress bar, use the same drawable for both Drawable[] drawables = {gdBackground, cdProgress, cdProgress}; LayerDrawable ld = new LayerDrawable(drawables); ld.setId(0, android.R.id.background); ld.setId(1, android.R.id.secondaryProgress); ld.setId(2, android.R.id.progress); bar.setProgressDrawable(ld);
由于我需要为每个(四个)进度条执行此操作,因此我将其全部封装在一个属于活动的 changeColors(TextProgressBar bar, int[] colors)
方法中。我可以在活动的 onCreate()
方法中调用该方法,或者在更改颜色的其他地方(例如,在偏好设置中)调用。
尽情享用!