@Override
public boolean onDown(MotionEvent e) { return false; }
@Override
public void onShowPress(MotionEvent e) { }
@Override
public boolean onSingleTapUp(MotionEvent e) { return false; }
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; }
@Override
public void onLongPress(MotionEvent e) { }
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; }
});
mGestureDetector.setOnDoubleTapListener(new OnDoubleTapListener() {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) { return false; }
@Override
public boolean onDoubleTap(MotionEvent e) { return false; }
@Override
public boolean onDoubleTapEvent(MotionEvent e) { return false; }
});
// 解决长按屏幕后无法拖动的问题
mGestureDetector.setIsLongpressEnabled(false);
imageView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
});
如果是监听滑动相关,建议在 onTouchEvent
中实现,如果要监听双击,那么就使用 GestureDectector
。
Scroller
弹性滑动对象,用于实现 View 的弹性滑动,Scroller 本身无法让 View 弹性滑动,需要和 View 的 computeScroll
方法配合使用。startScroll
方法是无法让 View 滑动的,invalidate
会导致 View 重绘,重回后会在 draw
方法中又会去调用 computeScroll
方法,computeScroll
方法又会去向 Scroller 获取当前的 scrollX 和 scrollY,然后通过 scrollTo
方法实现滑动,接着又调用 postInvalidate
方法如此反复。
Scroller mScroller = new Scroller(mContext);
private void smoothScrollTo(int destX) {
int scrollX = getScrollX();
int delta = destX - scrollX;
// 1000ms 内滑向 destX,效果就是慢慢滑动
mScroller.startScroll(scrollX, 0 , delta, 0, 1000);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
View 的滑动
scrollTo/scrollBy
适合对 View 内容的滑动。scrollBy
实际上也是调用了 scrollTo
方法:
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
mScrollX的值等于 View 的左边缘和 View 内容左边缘在水平方向的距离,mScrollY的值等于 View 上边缘和 View 内容上边缘在竖直方向的距离。scrollTo
和 scrollBy
只能改变 View 内容的位置而不能改变 View 在布局中的位置。
- 使用动画
操作简单,主要适用于没有交互的 View 和实现复杂的动画效果。
- 改变布局参数 操作稍微复杂,适用于有交互的 View.
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
params.width += 100;
params.leftMargin += 100;
view.requestLayout();
//或者 view.setLayoutParams(params);
View 的事件分发
点击事件达到顶级 View(一般是一个 ViewGroup),会调用 ViewGroup 的 dispatchTouchEvent 方法,如果顶级 ViewGroup 拦截事件即 onInterceptTouchEvent 返回 true,则事件由 ViewGroup 处理,这时如果 ViewGroup 的 mOnTouchListener 被设置,则 onTouch 会被调用,否则 onTouchEvent 会被调用。也就是说如果都提供的话,onTouch 会屏蔽掉 onTouchEvent。在 onTouchEvent 中,如果设置了 mOnClickListenser,则 onClick 会被调用。如果顶级 ViewGroup 不拦截事件,则事件会传递给它所在的点击事件链上的子 View,这时子 View 的 dispatchTouchEvent 会被调用。如此循环。
-
ViewGroup 默认不拦截任何事件。ViewGroup 的 onInterceptTouchEvent 方法默认返回 false。
-
View 没有 onInterceptTouchEvent 方法,一旦有点击事件传递给它,onTouchEvent 方法就会被调用。
-
View 在可点击状态下,onTouchEvent 默认会消耗事件。
-
ACTION_DOWN 被拦截了,onInterceptTouchEvent 方法执行一次后,就会留下记号(mFirstTouchTarget == null)那么往后的 ACTION_MOVE 和 ACTION_UP 都会拦截。`
在 Activity 中获取某个 View 的宽高
- Activity/View#onWindowFocusChanged
// 此时View已经初始化完毕
// 当Activity的窗口得到焦点和失去焦点时均会被调用一次
// 如果频繁地进行onResume和onPause,那么onWindowFocusChanged也会被频繁地调用
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
int width = view.getMeasureWidth();
int height = view.getMeasuredHeight();
}
}
- view.post(runnable)
// 通过post可以将一个runnable投递到消息队列的尾部,// 然后等待Looper调用次runnable的时候,View也已经初
// 始化好了
protected void onStart() {
super.onStart();
view.post(new Runnable() {
@Override
public void run() {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
- ViewTreeObserver
// 当View树的状态发生改变或者View树内部的View的可见// 性发生改变时,onGlobalLayout方法将被回调
protected void onStart() {
super.onStart();
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@SuppressWarnings(“deprecation”)
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
Draw 的基本流程
// 绘制基本上可以分为六个步骤
public void draw(Canvas canvas) {
…
// 步骤一:绘制View的背景
drawBackground(canvas);
…
// 步骤二:如果需要的话,保持canvas的图层,为fading做准备
saveCount = canvas.getSaveCount();
…
canvas.saveLayer(left, top, right, top + length, null, flags);
…
// 步骤三:绘制View的内容
onDraw(canvas);
…
// 步骤四:绘制View的子View
dispatchDraw(canvas);
…
// 步骤五:如果需要的话,绘制View的fading边缘并恢复图层
canvas.drawRect(left, top, right, top + length, p);
…
canvas.restoreToCount(saveCount);
…
// 步骤六:绘制View的装饰(例如滚动条等等)
onDrawForeground(canvas)
}
自定义 View
- 继承 View 重写
onDraw
方法
主要用于实现一些不规则的效果,静态或者动态地显示一些不规则的图形,即重写 onDraw
方法。采用这种方式需要自己支持 wrap_content,并且 padding 也需要自己处理。
- 继承 ViewGroup 派生特殊的 Layout
主要用于实现自定义布局,采用这种方式需要合适地处理 ViewGroup 的测量、布局两个过程,并同时处理子元素的测量和布局过程。
- 继承特定的 View
用于扩张某种已有的View的功能
- 继承特定的 ViewGroup
用于扩张某种已有的ViewGroup的功能
进程
==
进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
当某个应用组件启动且该应用没有运行其他任何组件时,Android 系统会使用单个执行线程为应用启动新的 Linux 进程。默认情况下,同一应用的所有组件在相同的进程和线程(称为“主”线程)中运行。
各类组件元素的清单文件条目<activity>
、<service>
、<receiver>
和 <provider>
—均支持 android:process 属性,此属性可以指定该组件应在哪个进程运行。
进程生命周期
1、前台进程
-
托管用户正在交互的 Activity(已调用 Activity 的
onResume()
方法) -
托管某个 Service,后者绑定到用户正在交互的 Activity
-
托管正在“前台”运行的 Service(服务已调用
startForeground()
) -
托管正执行一个生命周期回调的 Service(
onCreate()
、onStart()
或onDestroy()
) -
托管正执行其
onReceive()
方法的 BroadcastReceiver
2、可见进程
-
托管不在前台、但仍对用户可见的 Activity(已调用其
onPause()
方法)。例如,如果 re前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。 -
托管绑定到可见(或前台)Activity 的 Service