一步步搞定自定义VIEW(一)

View的作用

android基于linux内核,也就是说操作系统层面的工作由linux来做。android程序运行于DalvikVM,这个设计来自于JVM。在这两方面,android主要是靠借鉴。android系统需要解决的重要工作就是全新的交互方式,区别于功能机时代的按键方式,而这个功能的实现,在用户层面来讲,就是View。所以,View的两个设计初衷就是要显示内容,以及响应事件。
我们从部分View的注释中可以看出来:

 * This class represents the basic building block for user interface components. A View
 * occupies a rectangular area on the screen and is responsible for drawing and
 * event handling. 

View处理事件

在Java中一般接口是用来规范行为用的,先看View实现的接口:

@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
        //省略
        }

在屏幕上点击Button时,android的inputManager接收到这个事件时,放在一个队列中,而另一个线程,负责从这个队列中拿出一个个事件进行分发。inputService通过pipe和binder,将这个事件交由于给windowMangerSevice,wms将这个事件交给ams,再通过binder将事件传给activity,activity交给phoneWindow,这个便是整个屏幕的rootview,最终,调用目标View的ontouchevent这个方法。如果是按下了back键,又是如何传递的呢?android的所有事件,包括触摸事件和按键事件,都是由同一个单独的进程来负责。所不同的是在client中,处理的对象不同。touch事件,是有先后顺序的(这个后面分析),而key事件,activity和view均可处理(均实现了KeyEvent.CallBack)。以onkeyDown为例
View中

public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (KeyEvent.isConfirmKey(keyCode)) {
            if ((mViewFlags & ENABLED_MASK) == DISABLED) {
                return true;
            }

            if (event.getRepeatCount() == 0) {
                // Long clickable items don't necessarily have to be clickable.
                final boolean clickable = (mViewFlags & CLICKABLE) == CLICKABLE
                        || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE;
                if (clickable || (mViewFlags & TOOLTIP) == TOOLTIP) {
                    // For the purposes of menu anchoring and drawable hotspots,
                    // key events are considered to be at the center of the view.
                    final float x = getWidth() / 2f;
                    final float y = getHeight() / 2f;
                    if (clickable) {
                        setPressed(true, x, y);
                    }
                    checkForLongClick(0, x, y);
                    return true;
                }
            }
        }

        return false;
    }

activity中:

 public boolean onKeyDown(int keyCode, KeyEvent event)  {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (getApplicationInfo().targetSdkVersion
                    >= Build.VERSION_CODES.ECLAIR) {
                event.startTracking();
            } else {
                onBackPressed();
            }
            return true;
        }

        if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
            return false;
        } else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
            Window w = getWindow();
            if (w.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
                    w.performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, keyCode, event,
                            Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {
                return true;
            }
            return false;
        } else if (keyCode == KeyEvent.KEYCODE_TAB) {
            // Don't consume TAB here since it's used for navigation. Arrow keys
            // aren't considered "typing keys" so they already won't get consumed.
            return false;
        } else {
            // Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_*
            boolean clearSpannable = false;
            boolean handled;
            if ((event.getRepeatCount() != 0) || event.isSystem()) {
                clearSpannable = true;
                handled = false;
            } else {
                handled = TextKeyListener.getInstance().onKeyDown(
                        null, mDefaultKeySsb, keyCode, event);
                if (handled && mDefaultKeySsb.length() > 0) {
                    // something useable has been typed - dispatch it now.

                    final String str = mDefaultKeySsb.toString();
                    clearSpannable = true;

                    switch (mDefaultKeyMode) {
                    case DEFAULT_KEYS_DIALER:
                        Intent intent = new Intent(Intent.ACTION_DIAL,  Uri.parse("tel:" + str));
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        startActivity(intent);
                        break;
                    case DEFAULT_KEYS_SEARCH_LOCAL:
                        startSearch(str, false, null, false);
                        break;
                    case DEFAULT_KEYS_SEARCH_GLOBAL:
                        startSearch(str, false, null, true);
                        break;
                    }
                }
            }
            if (clearSpannable) {
                mDefaultKeySsb.clear();
                mDefaultKeySsb.clearSpans();
                Selection.setSelection(mDefaultKeySsb,0);
            }
            return handled;
        }
    }

再看看KeyEvent中:

 /** Whether key will, by default, trigger a click on the focused view. * @hide */
    public static final boolean isConfirmKey(int keyCode) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_CENTER:
            case KeyEvent.KEYCODE_ENTER:
            case KeyEvent.KEYCODE_SPACE:
            case KeyEvent.KEYCODE_NUMPAD_ENTER:
                return true;
            default:
                return false;
        }
    }

可以看出,activity和view都可以处理key事件,只是view处理的如空格,换行等事件,而activity处理回退,全局搜索等事件.

显示

public class MyView extends View {

    private Paint paint;

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

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        paint.setColor(Color.RED);
    }

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

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(getWidth()/2,getHeight()/2,200,paint);
    }
}
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<com.example.administrator.testview.MyView  android:id="@+id/myview" android:layout_width="wrap_content" android:layout_height="wrap_content" />
LinearLayout>

效果为下图:
一步步搞定自定义VIEW(一)_第1张图片

这个例子说明了什么?
1.继承VIEW
2.在XML中添加,属性为wrap_content。
3.在第二个构造函数中初始化。

这不是个完整的的自定义VIEW,里面只重写了ondraw,没有重写onmeasure,也没有在xml中添加自定义属性。因为宽高是wrap_content,显示的效果就是全屏,如果要给出特定的宽高,就必须重写onmeasure。
我们来看看为什么。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

这段是VIEW的onmeasure方法,最终结果便是setMeasuredDimension这个方法确定,你可以设定任意值,比如说setMeasuredDimension(100,100),这样大小就是100*100。

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;

        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

从上面代码可以看出specMode为MeasureSpec.UNSPECIFIED的时候,也就是wrap_content的时候,给的是size,也就是默认值,来看看这个值是多少。

 protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

    protected int getSuggestedMinimumHeight() {
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

    }

这下就真相大白了,默认的是MATCH_PARENT。所以为什么是全屏。

你可能感兴趣的:(ANDROID,UI)