您的位置:首页 > 产品设计 > UI/UE

OPhone自定义UI控件的实现原理解析(浮动导航栏)

2010-09-17 13:27 447 查看
导读:本文来自《程序员》杂志2010年第7期,作者是中国移动通信研究院终端技术研究所项目经理,OPhone业务组成员徐志德,徐志德主要负责OPhone平台的浏览器和WAP等模块的研发和管理。此文结合具体实例,详细介绍了OPhone平台中自定义UI控件的实现。

在OPhone应用中,绝大部分的用户交互是通过View和ViewGroup等UI控件来实现的。OPhone系统中已经预置了丰富的UI控件来满足开发者使用,例如小控件widgets(包括Button、TextView、EditText)和用于屏幕制图的各种布局类(layouts)等,但如果现有控件不能满足需求,开发者可以自定义UI控件来实现更全面的控制。本文中就介绍了一种自定义UI组件控件的实现。

OPhone平台UI控件扩展介绍

OPhone平台大致有三种创建新的定制UI控件的方法。

自定义控件的方法有三个:

1 .通过开发者自己类扩展OPhone平台现有的View类或其子类。

2 . 重载View基类中的各种接口方法来重新画图,执行功能和处理按键操作。

3 .使用自己的扩展类。这些扩展类可以使用在它所基于的View类上。

基于方法1的定制控件中,开发者可以通过XML layout文件来定义其包含的内容、层次关系和位置布局。以图1中LinearLayout的多个View和ViewGroup元素结构为例,其中的每一个元素都是一个View或者一个ViewGroup。每一个XML结点元素的都对应其Java类的名称和位置等参数。当程序中载入XML资源文件之后,OPhone系统初始化为运行时对象。

 



 

    图1  LinearLayout中的元素结构

通过方法2,重载View基类中的各种接口,开发者可以对一个屏幕元素的外观和功能做精准的控制,例如重载一个可编辑文本控件在屏幕上展现的方式:文字大小、多行显示和背景颜色等。开发者还可以通过方法3,创建一个完全的自渲染的View类型,或合并一系列View控件构成一个单独的控件,还可以重载一个可编辑文本组件控件在屏幕上展现的方式:文字大小,多行显示和背景颜色等等。开发者还可以捕捉一个图形界面上的点击和拖拽等用户操作,来实现自定义开发动作。在游戏开发中这是很重要的实现方法。作为一个移动终端上定制控件开发,除了功能还要考虑屏幕尺寸、分辨率和移动终端功耗的优化。

下面通过一个例子来介绍如何定制开发一个UI控件并在自己的应用中使用。对于接口的详细信息请参考基类View的参考文档。

自定义的悬浮工具条控件的实现样例。

这是一个在浏览器上实现的自定义工具条的例子,这个工具条悬浮在网页页面上,有隐藏和显示两种状态。如图2所示,工具条上有返回、向前、放大、缩小和添加书签等功能键,每个功能键会根据当前页面状态置为enable和disable两种状态。



 

        图2  悬浮工具条的结构示意图

根据需求,工具条需要显示在页面的下侧中间位置,在一个矩形空间内定义6个功能键。我们选择扩展LinearLayout类来实现这个悬浮工具条,其中布局基类LinearLayout是ViewGroup的一个子类,可把六个Icon按键依次定义在这个图组内。使用ViewGroup作为基类来开发定制控件有很多好处,开发者可以通过像定义一个activity 窗口一样来定义XML文件和组织ViewGroup的布局。同样也可以在代码中定义布局来创建这个定制控件。自定义类中的重要接口都已经被定义,引用者不必再重载,这样开发出来的定制化图像,可以很方便地作为一个对象被引用。

我们定义控件为ControlPanel类。构造函数里首先定义LinearLayout在父窗口里显示的位置和按键对象。

 public  class [代码开始之前,加上]:

根据需求,我们得需要如下方式来定义界面,在自定义的方法中如何如何。

这段代码是一个构造函数方法,好歹给点解释。

ControlPanel extends LinearLayout {

        public static final FrameLayout.LayoutParams DEFAULT_PARAMS =

            new FrameLayout.LayoutParams(

                    ViewGroup.LayoutParams.FILL_PARENT,

                    ViewGroup.LayoutParams.WRAP_CONTENT,

                    Gravity.BOTTOM | Gravity.RIGHT);

        private final ZoomButton[] mButtons;

        protected ControlPanel(Context context, AttributeSet attrs) {

            super(context, attrs);

            this.setLayoutParams(DEFAULT_PARAMS);

            mButtons = new ZoomButton[MAX_BUTTONS];

            LayoutInflater inflater = (LayoutInflater) context.

    getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            inflater.inflate(R.layout.control_panel, this, true); // we are the parent               

            initButtons();

        }

    }

以上是通过扩展LinearLayout类来实现一个完全的定制组件控件,其中布局基类LinearLayout是ViewGroup的一个子类。使用LinearLayout等ViewGroup作为基类来开发定制组件控件有很多好处,开发者可以通过像定义一个activity 窗口一样来定义XML文件去组织这个定制化ViewGroup的布局。同样开发者可以选择在代码中定义布局来创建这个定制类; 自定义类中的重要接口都已经被定义,开发者不必再重载;这样开发出来的定制化图像,可以很方便的作为一个对象被引用。

工具条有隐藏和显示两种状态,初始状态为隐藏,用户可以通过滑动屏幕来触发事件,调出工具条。控件提供接口来根据页面状态决定显示与否。显示和隐藏的接口如下。

 public void show() {

            if(getVisibility() == View.VISIBLE) {

                return;

            }

            updateIconState();

            fade(View.VISIBLE, 0.0f, 1.0f);

        }   

        public void hide() {

            if(getVisibility() != View.VISIBLE) {

                return;

            }

            fade(View.GONE, 1.0f, 0.0f);

        }

在初始化阶段,通过函数initButtons()来为每一个图标的点击事件来定义特定功能,

通过创建控件中各个按钮的Click事件Listener来在应用中处理每个按键点击后的处理事件,工具条控件中每个按键点击后分别会触发原应用的上一页、下一页、最前页、最后页、放大和缩小等功能。如果需要复用这个工具条的话可以重新定义这些案件的功能定义。

private void initButtons() {

            ZoomButton button = null;

            int index = 0;

            button = (ZoomButton) findViewById(R.id.button0);

            button.setBackgroundResource(R.drawable.ic_toolbar_back);

            setButton(index++, button, new View.OnClickListener() {

                public void onClick(View v) {

                        goBack();   

                }

            });

            button = (ZoomButton) findViewById(R.id.button1);

            button.setBackgroundResource(R.drawable.ic_toolbar_forward);

            setButton(index++, button, new View.OnClickListener() {

                public void onClick(View v) {

                    if(View == null) return;

                    if(View.canGoForward()) {

                        goForward();

                        mButtons[0].setEnabled(true);

                        resetZoomState();

                    }else {

                        mButtons[1].setEnabled(false);

                    }

                }

            });

    // 定义其他几个按键的功能

        }

        public void setButton(int index, ZoomButton button, OnClickListener listener) {

            if(index >= MAX_BUTTONS) {

                return;

            }

            mButtons[index] = button;

            mButtons[index].setOnClickListener(listener);

        }

通过updateIconState()接口来更新所有的icon状态,例如根据应用当前页上下文,设置最前页、最后页等icon为disable状态。

private void updateIconState() {

            if(!UPDATE_ICON) return;

            WebView View = get YourWebView ();

            if(View == null) return;

            updateBackForwardState(View); 

            updateZoomState(View);

        }

自定义控件中,通过接收应用窗口上的触屏动作(MotionEvent)来决定是否显示出来;在showIfNeeded()中,通过判断目前滑动的范围作为判决条件;当用户点击屏幕时,对屏幕的操作过程会依次被系统区分为MotionEvent.ACTION_DOWN(用户点击到触摸屏时触发该信号)、MotionEvent.ACTION_MOVE(当用户手指在屏幕上滑动时会被系统识别为该信号)和MotionEvent.ACTION_UP(用户抬起手指)等几个主要动作。在这个自定义组件控件中,当接收到用户的MotionEvent.ACTION_DOWN时,控件开始记录点击点的坐标,然后在用户的MotionEvent.ACTION_MOVE中,记录用户的移动坐标。当用户操作达到一定阈值时,更新控件的状态为显示。

private int mMotionX = 0;

        private int mMotionY = 0;

        public void showIfNeeded(MotionEvent event) {

            if(mInstance == null) return;

            int x = 0, y = 0;

            switch(event.getAction()) {

            case MotionEvent.ACTION_DOWN:

                mMotionX = (int)event.getX();

                mMotionY = (int)event.getY();

                break;

            case MotionEvent.ACTION_MOVE:

                x = (int)event.getX();

                y = (int)event.getY();

                if(Math.abs(x - mMotionX) > 5 || Math.abs(y - mMotionY) > 5) {

                    mInstance.show();

                }

                mMotionX = x;

                mMotionY = y;

                break;

            default:

                break;

            }

        }

fade(int visibility, float startAlpha,float endAlpha)定义了工具条的退出效果,通过调用系统的接口AlphaAnimation,做一个渐变为透明的动画效果,淡出的效果会持续0.5秒。

 private void fade(int visibility, float startAlpha, float endAlpha) {

            AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha);

            anim.setDuration(500);

            startAnimation(anim);

            setVisibility(visibility);

        }   

控件的内容和布局通过layout文件来定义,其中android:background为整个控件的背景图片,android:id指明图标的标识ID,android:gravity定义图标排列的规则,默认情况下,控件位于屏幕的左上角。可以使用 android:layout_gravity、android:gravity和android:width三个属性值,实现控件的九宫格定位。如何将控件置于下方中间呢?只需将android:gravity的值改为center即可。这个XML文件命名为control_panel.xml,存放在工程中的/ res/layout/路径下,供应用程序调用。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

        android:layout_width="fill_parent"

        android:layout_height="wrap_content"

        android:background="@drawable/background_pic"

    android:layout_gravity="bottom"

    android:gravity="center_vertical">

        <LinearLayout android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_weight="1"

            android:gravity="center_horizontal"

            >

        <ZoomButton android:id="@+id/button0"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:scaleType="center"

            />

        </LinearLayout>

        <LinearLayout android:layout_width="wrap_content"

            android:layout_weight="1"

            android:gravity="center_horizontal"

            android:layout_height="wrap_content">

        <ZoomButton android:id="@+id/button1"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:gravity="center_horizontal"

            android:scaleType="center"

            />

    </LinearLayout>

            <!-- 其他4个按键,按照同样规则定义 -->

      </LinearLayout>

ControlPanel类可以被添加到一个FrameLayout ViewGroup中,作为一部分,显示在一个应用中。在应用的Activity中使用setContentView()来把ViewGroup的根节点传递进去, 就可以把这个ViewGroup的层次树添加到屏幕上去展现。例如在某应用中其主框架FrameLayout的layout文件名称是main_frame.xml。可以通过如下方式添加ControlPanel到FrameLayout中。

private FrameLayout mFrameLayout;

    static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =

            new FrameLayout.LayoutParams(

            ViewGroup.LayoutParams.FILL_PARENT,

            ViewGroup.LayoutParams.FILL_PARENT);

    FrameLayout frameLayout = new FrameLayout(this);

    setContentView(frameLayout);

    LayoutInflater LI = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    mFrameLayout = (FrameLayout) LI.from(this).inflate(R.layout. main_frame, null);

    frameLayout.addView(mFrameLayout, COVER_SCREEN_PARAMS);

    …

    initControlPanel();

    mControlPanel.show();

    mFrameLayout.addView(mControlPanel);

    其中对应的初始化ControlPanel的接口如下,并且将其出示状态设置为消失状态。

    private ControlPanel mControlPanel;

    private void initControlPanel() {

    mControlPanel = new ControlPanel(this);

    mControlPanel.setVisibility(View.GONE);

    }

将这个控件使用在OPhone浏览器中后,打开的效果如图3,其中六个按键的功能定义为前进,后退,多窗口管理,添加书签和页面方法缩小。由于页面没有发生过后退动作,所以第二个图标为disable状态。

 



 

    图3  control_panel在OPhone浏览器中效果图

在这个例子的基础上还可以通过计时器来实现浮动工具条在一段时间后消失的效果,或者通过资源中设置style或者背景图片来增加半透明效果等优化方法,实现一个更绚丽丰富的自定义View控件。

总结

在OPhone平台内,View和ViewGroup作为应用和用户的接口作用十分重要,当应用场景非常复杂时,开发者可以根据具体需求,综合View和ViewGroup类的特点,与时间和用户动作相结合,辅助以风格等控件属性细化设定,实现千变万化的定制化View控件。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息