<p>好了,今天我们来真正自己动手制作控件了,在本章中我们会从最简单开始。虽然是简单,但你必须已经能自己做 Activity 等最基本的东西。</p> <p>注意,我不会:</p> <ol> <li>控件中不会使用任何 res/ 下的文件支持,因为这对于为自己建立一个控件库来说不是必须的,相反它让设计变的复杂,做一个简单的控件在多个文件中跳来跳去可不是什么好玩的事,特别是未来你需要把控件复制到你的项目中使用时,如果只需要复制一个 .java 文件是多么简单,你不用为了复制相关副属资源文件找来找去了,不是吗。当然,如果你的确需要设计时能设定一大堆自己的属性什么的,你完全可以按第一章提到的方式去实现。 </li> <li>不会使用太多 Android 框架下的帮助类与方法,因为我也在看呢 :)。如果一定要找个理由来说的话,我相信专为控件而定制的帮助方法一定比通用版本的简单,从而更有效率。 </li> <li>从第一章到最后一章,几乎不会找到有一个函数始终是一样的。因为我们是从简单开始,但最后,我们希望的是实用好用,而不是个 TEST 而已。 </li> <li>我不会写大堆的文字说明来解析每一行代码,因为大部分东西我已经以注释的样式写在代码里了。我不需要你忘记了什么之后再来的点击量 :) </li> </ol> <p> </p> <h2>目标</h2> <p>来说说我们这次是目标 – LCDView。</p> <p>从名字可以看出,它是一个模拟 LCD 效果的视图或直白说成是模拟点阵屏幕好了。</p> <p> </p> <h2>版本一</h2> <p>首先按您喜欢的名称创建一个新的 Android 项目,然后新建一个类 LCDView,实现如下:</p> <div> <pre><span style="color: #0000ff">public</span> <span style="color: #0000ff">class</span> LCDView <span style="color: #0000ff">extends</span> View {
<span style="color: #0000ff">private</span> String text = "<span style="color: #8b0000">LCD 自定义视图</span>";
<span style="color: #0000ff">private</span> Paint mTextPaint = <span style="color: #0000ff">new</span> Paint();
<span style="color: #0000ff">private</span> Paint mLinePaint = <span style="color: #0000ff">new</span> Paint();
<span style="color: #0000ff">private</span> Rect mBounds = <span style="color: #0000ff">new</span> Rect();
<span style="color: #0000ff">private</span> Point mTextPos = <span style="color: #0000ff">new</span> Point();
<span style="color: #0000ff">private</span> Rect mTextBounds = <span style="color: #0000ff">new</span> Rect();
<span style="color: #0000ff">public</span> LCDView(Context context, AttributeSet attrs) {
<span style="color: #0000ff">super</span>(context, attrs);
init();
}
<span style="color: #0000ff">private</span> <span style="color: #0000ff">void</span> init() {
<span style="color: #008000">// 默认背景为黑色</span>
setBackgroundColor(Color.BLACK);
mLinePaint.setColor(0xcc000000);
mLinePaint.setStyle(Style.STROKE);
mLinePaint.setStrokeWidth(1);
<span style="color: #008000">// 默认文本为白色</span>
mTextPaint.setColor(Color.WHITE);
mTextPaint.setTextSize(36);
mTextPaint.setTextAlign(Align.CENTER);
}
@Override
<span style="color: #0000ff">protected</span> <span style="color: #0000ff">void</span> onSizeChanged(<span style="color: #0000ff">int</span> w, <span style="color: #0000ff">int</span> h, <span style="color: #0000ff">int</span> oldw, <span style="color: #0000ff">int</span> oldh) {
<span style="color: #0000ff">super</span>.onSizeChanged(w, h, oldw, oldh);
<span style="color: #008000">// 计算我们可能用于绘制内容的区域</span>
mBounds.set(getPaddingLeft(), getPaddingTop(), w - getPaddingLeft() - getPaddingRight(), h - getPaddingTop() - getPaddingBottom());
recalcTextPoint();
}
<span style="color: #0000ff">private</span> <span style="color: #0000ff">void</span> recalcTextPoint() {
<span style="color: #008000">// 我们将在视图的正中间绘制文本,这里计算文本需要绘制的位置</span>
<span style="color: #0000ff">int</span> textHeight = 0;
<span style="color: #0000ff">if</span>(text != <span style="color: #0000ff">null</span> && !text.isEmpty()) {
mTextPaint.getTextBounds(text, 0, 1, mTextBounds);
textHeight = mTextBounds.height();
}
mTextPos.x = mBounds.centerX();
mTextPos.y = mBounds.centerY() + (textHeight / 2);
}
@Override
<span style="color: #0000ff">protected</span> <span style="color: #0000ff">void</span> onDraw(Canvas canvas) {
<span style="color: #0000ff">super</span>.onDraw(canvas);
<span style="color: #0000ff">if</span>(text == <span style="color: #0000ff">null</span> && text.isEmpty()) {
<span style="color: #0000ff">return</span>;
}
canvas.drawText(text, mTextPos.x, mTextPos.y, mTextPaint);
<span style="color: #008000">// 相当直接的做法,后面将变很多次</span>
<span style="color: #0000ff">for</span>(<span style="color: #0000ff">int</span> y=mBounds.top; y<mBounds.bottom; y+=2) {
canvas.drawLine(mBounds.left, y, mBounds.right, y, mLinePaint);
}
}
<span style="color: #008000">/***** 公共用户属性 *****/</span>
<span style="color: #008000">/**
* 获取当前显示文本
* @return
*/</span>
<span style="color: #0000ff">public</span> String getText() {
<span style="color: #0000ff">return</span> text;
}
<span style="color: #008000">/**
* 设置要显示的文本
* @param text
*/</span>
<span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> setText(String text) {
<span style="color: #0000ff">this</span>.text = text;
recalcTextPoint();
invalidate();
}
<span style="color: #008000">/**
* 获取当前文本颜色
* @return
*/</span>
<span style="color: #0000ff">public</span> <span style="color: #0000ff">int</span> getTextColor() {
<span style="color: #0000ff">return</span> mTextPaint.getColor();
}
<span style="color: #008000">/**
* 设置当前文本颜色
* @param color
*/</span>
<span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> setTextColor(<span style="color: #0000ff">int</span> color) {
mTextPaint.setColor(color);
invalidate();
}
<span style="color: #008000">/**
* 获取当前文本字体大小
* @return
*/</span>
<span style="color: #0000ff">public</span> <span style="color: #0000ff">float</span> getTextSize() {
<span style="color: #0000ff">return</span> mTextPaint.getTextSize();
}
<span style="color: #008000">/**
* 设置当前文本字体大小
* @param textSize
*/</span>
<span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> setTextSize(<span style="color: #0000ff">float</span> textSize) {
mTextPaint.setTextSize(textSize);
recalcTextPoint();
invalidate();
}
}</pre>
</div>
<br />
<p>完成了,它就是一个可用的视图了,打开 activity_main.xml 点左边的 “Custom & Library Views”然后点面板底部的 “Refresh”,看到它了吧,像一般自带控件一样,拖到设计界面里去,然后运行看看效果。</p>
<p>在主要的绘图代码里,我们通过绘制间隔为一个像素的半透明黑色横向线条,让它看起来像 LCD 一样了。</p>
<p>可以看到在几个给用户操作的 setter 中,有些调用了 recalcTextPoint() 与 invalidate(),而有些只调用了 invalidate() ,原则是只干你需要干的事,多的事咱不干~~~</p>
<p>现在让我们在添加一个按钮看看 setter 有木有效果:</p>
<ol> <li>再拖一个按钮到设计界面,修改 id 为 “buttonChangeColor” </li>
<li>切换到 MainActivity.java ,在 onCreate 之前加入如下代码: <div style="border-bottom: #ddd 1px solid; border-left: #ddd 1px solid; padding-bottom: 1em; margin: 0px 0px 1em; padding-left: 1em; padding-right: 1em; background: #f7f7f7; overflow: auto; border-top: #ddd 1px solid; border-right: #ddd 1px solid; padding-top: 1em"> <pre> <span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">final</span> <span style="color: #0000ff">int</span>[] colors = <span style="color: #0000ff">new</span> <span style="color: #0000ff">int</span>[]{ Color.WHITE, Color.BLUE, Color.GREEN, Color.RED, Color.YELLOW };
<span style="color: #0000ff">private</span> <span style="color: #0000ff">int</span> colorIndex = 0;
<span style="color: #0000ff">private</span> LCDView lcd;</pre>
</div>
</li>
<li>在 onCreate 内部的尾部加入加入代码: <div style="border-bottom: #ddd 1px solid; border-left: #ddd 1px solid; padding-bottom: 1em; margin: 0px 0px 1em; padding-left: 1em; padding-right: 1em; background: #f7f7f7; overflow: auto; border-top: #ddd 1px solid; border-right: #ddd 1px solid; padding-top: 1em"> <pre> lcd = (LCDView)findViewById(R.id.lCDView1); findViewById(R.id.buttonChangeColor).setOnClickListener(<span style="color: #0000ff">this</span>);</pre> </div> </li>
<li>为 MainActivity 类实现 OnClickListener 接口,添加实现函数: <div style="border-bottom: #ddd 1px solid; border-left: #ddd 1px solid; padding-bottom: 1em; margin: 0px 0px 1em; padding-left: 1em; padding-right: 1em; background: #f7f7f7; overflow: auto; border-top: #ddd 1px solid; border-right: #ddd 1px solid; padding-top: 1em"> <pre> @Override <span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> onClick(View v) { colorIndex++; colorIndex%=colors.length; lcd.setTextColor(colors[colorIndex]); }</pre> </div> </li>
<li>运行。 </li> </ol>
<p>好了,点击按钮试试,每一次点击文本颜色在白,蓝,绿,红,黄中转换,看起来还不错。</p>
<p>目前这个代码有两个问题,一个是如果用户调用 setBackgroundColor 修改了背景色,你的线条就曝光了;另一个问题,用 for 循环去画直线条这太低层次了,我们将在下一节中完善。 </p>