原型
在开发 UI 的时候,我们会用到各种各样的组件来丰富 UI 的交互体验,例如:文本输入框、滚动条、 进度条、单选框等,这些组件虽然有着不同的数据、方法、视觉效果和交互方式,但都能在 LCUI 中以相同的规则来工作,而这个规则就是原型。
原型方法
原型中记录了 LCUI 在更新和渲染组件时需要用到的方法,通过将这些方法与自定义函数绑定,可实现对组件的扩展。关于原型的定义,你可以在 include/LCUI/gui/widget_base.h 中找到。
接下来让我们深入了解原型中的各个方法的用途。
init
LCUIWidget_New()
函数在找到原型后,会调用 init()
函数按照该类型的组件预设的方法初始化组件实例,不同类型的组件都会有自己的数据以及其它相关的设置,这些数据可以在 init()
函数中初始化。
destroy
在组件被销毁时会调用 destroy()
函数,通常这个函数主要负责销毁组件的私有数据、解除相关设置等。
update
对于某些组件而言,预置的 CSS 属性无法满足需求,会需要用到扩展 CSS 属性,而这些扩展 CSS 属性的处理方法是 LCUI 无法知道的,因此,LCUI 在处理完预置的 CSS 属性后,会将剩余的 CSS 属性交给 update()
函数去处理。
runtask
在 LCUI 处理完组件预设的一些任务后,会调用 runtask()
去处理组件自己设定的一 些任务。
setattr
在 Widget_SetAttribute()
设置完属性后会调用它。
settext
当解析到 XML 文档元素内的文本结点时,会调用该函数让组件处理文本内容。
autosize
在计算组件宽高时,如果组件的宽高被设置为 auto,则会调用 autosize()
获取该组件的尺寸,如果未设置 autosize
,LCUI 会按照默认的方式计算组件的宽高。通常像文本显示(TextView)这类有自己内容的组件会需要这个函数来调整自身宽高以 适应文本内容。
resize
在组件宽高更新时调用。
paint
在 LCUI 按设定的样式绘制好组件后,会调用 paint()
绘制组件自己的内容。
proto
父级原型,用于访问父级原型的方法,这个属性不需要手动设置。
创建原型
创建原型需要用到 LCUIWidget_NewPrototype()
函数:
LCUI_WidgetPrototype LCUIWidget_NewPrototype(const char *name, const char *parent_name);
它的参数有两个:原型的名称、继承的父级原型的名称,创建完后会返回原型,如果已经存在同名的原型或者原型添加失败,则会返回 NULL。
以下代码展示了原型的创建方法,如需详尽的参考代码请查阅 LCUI 预置组件的代码(例如:src/gui/widget/textview.c)。
#include <LCUI.h>
#include <LCUI/gui/widget.h>
static struct MyWidgetModule {
LCUI_WidgetPrototype prototype;
// 其它用得到的数据
// xxxx
// ...
} my_widget;
static void MyWidget_OnInit(LCUI_Widget w)
{
// 初始化一些数据
}
static void MyWidget_OnDestroy(LCUI_Widget w)
{
// 释放相关数据
}
static void MyWidget_UpdateStyle(LCUI_Widget w)
{
// 处理扩展的样式属性
}
static void MyWidget_AutoSize(LCUI_Widget w, float *width, float *height)
{
// 根据自身的内容,计算合适的尺寸
}
static void MyWidget_OnTask(LCUI_Widget w)
{
// 处理积累的任务
}
static void MyWidget_OnPaint(LCUI_Widget w, LCUI_PaintContext paint)
{
// 利用 paint 上下文绘制自己的内容
}
static void MyWidget_OnParseText(LCUI_Widget w, const char *text)
{
// 处理 XML 解析器传来的文本内容
}
void LCUIWidget_AddMyWidget(void)
{
int i;
my_widget.prototype = LCUIWidget_NewPrototype("my-widget", NULL);
my_widget.prototype->init = MyWidget_OnInit;
my_widget.prototype->paint = MyWidget_OnPaint;
my_widget.prototype->destroy = MyWidget_OnDestroy;
my_widget.prototype->autosize = MyWidget_AutoSize;
my_widget.prototype->update = MyWidget_UpdateStyle;
my_widget.prototype->settext = MyWidget_OnParseText;
my_widget.prototype->runtask = MyWidget_OnTask;
// 如果需要用到全局的数据的话
// my_widget.xxxx = ???
// ...
}
使用私有数据
当组件的功能变多变复杂的时候,如果仅靠函数内的局部变量难以实现多个功能之间的数据共享的话,那么就会需要一个能在整个组件生命周期内有效的空间来存放数据,例如:文本编辑框,它会保存当前编辑的文本内容,调用相关函数可以对这个文本内容进行读写操作,在绘制时也会需要用到这些文本内容以在屏幕上绘制出相应的文字。
组件私有数据的操作函数有以下两个:
void *Widget_AddData(LCUI_Widget widget, LCUI_WidgetPrototype proto, size_t data_size);
void *Widget_GetData(LCUI_Widget widget, LCUI_WidgetPrototype proto);
从以上代码中可以看出组件私有数据有添加和获取这两种方法,私有数据是与组件原型绑定的,添加时需要指定具体的内存占用大小。添加后,可以调用 Widget_GetData()
函数获取私有数据,这个函数也同样需要指定原型。
以下是这两个函数的基本用法示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <LCUI.h>
#include <LCUI/gui/widget.h>
/** 组件私有数据的结构 */
typedef struct MyWidgetRec_ {
int a;
char b;
double c;
char *str;
} MyWidgetRec, *MyWidget;
static struct MyWidgetModule {
LCUI_WidgetPrototype prototype;
// 其它用得到的数据
// xxxx
// ...
} self;
static void MyWidget_OnInit(LCUI_Widget w)
{
MyWidget data;
const size_t size = sizeof(MyWidgetRec);
data = Widget_AddData(w, self.prototype, size);
// 初始化私有数据
data->a = 123;
data->b = 'b';
data->c = 3.1415926;
data->str = malloc(256 * sizeof(char));
strcpy(data->str, "this is my widget.");
printf("my widget is inited.\n");
}
static void MyWidget_OnDestroy(LCUI_Widget w)
{
MyWidget data = Widget_GetData(w, self.prototype);
// 释放私有数据占用的内存资源
free(data->str);
printf("my widget is destroied.\n");
}
void LCUIWidget_AddMyWidget(void)
{
int i;
self.prototype = LCUIWidget_NewPrototype("mywidget", NULL);
self.prototype->init = MyWidget_OnInit;
self.prototype->destroy = MyWidget_OnDestroy;
// 如果全局用得到的数据的话
// self.xxxx = ???
}