Android 2.2的锁屏界面如图1所示,左右两侧是2个滑动条,正常状态下左边的是解锁,右边的是声音的开关,来电话时,左边是接听电话的解锁,右边是不接听来电的解锁。
图1
当我们滑动解锁按钮时,右边会有一个绿色目标点,当滑动左边的解锁按钮到达右边的目标点时,解锁就成功了,然后就能够操作主界面了。如图2所示。
图2
问题:每次向右滑动解锁按钮时,都滑动的很远,也就是说,右边的那个绿色目标点很远,有些不太方便,那能否将这个点向左边移动一些呢?这样滑动的距离就少一些,如图3所示。
图3
答案:我们可以修改2.2源码中有关锁屏界面的部分代码就可以实现这个绿色的目标点左移了,而且解锁按钮滑动距离会变的很短,同时还可以把这个按钮做成透明的只显示一个按钮,不显示那个滑动条bar。
如何修改呢?
方法:
1) 锁屏界面主要有2个java文件、2个xml文件和一些图片文件。
frameworks/base/core/java/com/android/internal/widget/SlidingTab.java
frameworks/base/policy/src/com/android/internal/policy/impl/LockScreen.java
frameworks/base/core/res/res/layout/keyguard_screen_tab_unlock_.xml
frameworks/base/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
2) 在SlidingTab.java文件中,可以使得滑动条向右移动的近一点,同时可以使得绿色的目标点向左移动;
3) 在LockScreen.java文件中,可以使得滑动条的颜色和背景图片换成定制的,也可以使得绿色的目标点变成定制的图片;
4) keyguard_screen_tab_unlock_.xml是竖屏的锁屏界面布局文件,keyguard_screen_tab_unlock_land.xml是横屏的锁屏界面布局文件。可以在此修改数字时钟、日期和时间、充电的图标显示等的布局、大小、边距等;
具体代码分析和修改步骤:
1. SlidingTab.java
首先我们看到这个类是继承android.view.ViewGroup的,说明这个类是一个布局类的java文件。再打开keyguard_screen_tab_unlock_.xml 或者 keyguard_screen_tab_unlock_land.xml 文件会发现里面有一个标签元素:
<com.android.internal.widget.SlidingTab
android:id="@+id/tab_selector"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginRight="80dip"
/>
从这里我们可以看出,其实这个标签元素正好对应一个布局。平时我们都是使用
<RelativeLayout
……>
……
</RelativeLayout>
这样的标签来表示一个布局,而在此处我们却使用了一个java类文件来表示这个布局,这就很有意思了。其实这个含义很明显:利用java类文件实现布局比用纯粹的标签更加灵活,会实现很多标签无法首先的功能。
理解上面一点之后,再看看这个SlidingTab.java文件,里面定义了一个内部类:private static class Slider。
这个Slider包含了三个子对象:tab、text、target。
tab:这个tab就是在默认情况下显示的那个滑动条,如图4所示:
图4
text:当拖动左边的滑动条时,会出现“Unlock”文本,就是这个text。如图5所示。
图5
target: 当触摸到这个滑动条时,左、右边会各有一个点,就是目标点target,他起到一个临界指示作用,每次拖动滑动条达到这个临界点时就会触发解锁事件,解锁结束后就可以使用主界面了。如图6所示。要注意的是:这个目标点只是起到临界指示作用,没有别的含义。真正的临界点时看不见的,为了起到一个视觉的认识,在这里就使用这样一个目标点来代表临界点,下面会在代码中解释真正的临界点在哪里。
图6
在这个Slider内部类中有一个布局方法:void layout(int l, int t, int r, int b, int alignment)
核心代码是:
if (alignment == ALIGN_LEFT || alignment == ALIGN_RIGHT) {
// horizontal
Log.i(TAG,"Is this horizontal ?");
final int targetTop = (parentHeight - targetHeight) / 2;
final int targetBottom = targetTop + targetHeight;
final int top = (parentHeight - handleHeight) / 2;
final int bottom = (parentHeight + handleHeight) / 2;
if (alignment == ALIGN_LEFT) {
Log.i(TAG,"Is this leftBar? ");
// modified by zhuluofeng 2011-11-15
// 0 to 0+50,leftTarget to leftTarget+500
tab.layout(0, top, handleWidth, bottom);
text.layout(0 - parentWidth, top, 0, bottom);
text.setGravity(Gravity.RIGHT);
target.layout(leftTarget, targetTop, leftTarget + targetWidth, targetBottom);
Log.i(TAG,"targetTop is: " + targetTop);
Log.i(TAG,"targetBottom is: " + targetBottom);
alignment_value = l;
} else {
Log.i(TAG,"Is this RightBar?");
tab.layout(parentWidth - handleWidth, top, parentWidth, bottom);
text.layout(parentWidth, top, parentWidth + parentWidth, bottom);
target.layout(rightTarget, targetTop, rightTarget + targetWidth, targetBottom);
text.setGravity(Gravity.TOP);
alignment_value = r;
}
} else {
// vertical
Log.i(TAG,"Is this vertical ?");
final int targetLeft = (parentWidth - targetWidth) / 2;
final int targetRight = (parentWidth + targetWidth) / 2;
final int top = (int) (THRESHOLD * parentHeight) + handleHeight / 2 - targetHeight;
final int bottom = (int) ((1.0f - THRESHOLD) * parentHeight) - handleHeight / 2;
if (alignment == ALIGN_TOP) {
Log.i(TAG,"Is this TopBar ?");
tab.layout(left, 0, right, handleHeight);
text.layout(left, 0 - parentHeight, right, 0);
target.layout(targetLeft, top, targetRight, top + targetHeight);
alignment_value = t;
} else {
Log.i(TAG,"Is this BottomBar ?");
// modified by zhuluofeng 2011-11-15
// bottom to bottom+500, parentHeight - handleHeight to parentHeight - handleHeight-50
// the bottom is the right side
tab.layout(left, parentHeight - handleHeight, right, parentHeight);
text.layout(left, parentHeight, right, parentHeight + parentHeight);
target.layout(targetLeft, bottom, targetRight, bottom + targetHeight);
Log.i(TAG,"the vertical bottom is: " + bottom);
Log.i(TAG,"the vertical targetHeight is: " + targetHeight);
alignment_value = b;
}
}
这段代码其实就一个if else控制语句,if (alignment == ALIGN_LEFT || alignment == ALIGN_RIGHT) 表示:
手机处在竖屏时(即滑动条处在水平方向),如果拖动左边的滑动条或者拖动右边的滑动条时的处理方法。
else中的代码表示手机处于横屏时(即滑动条处在竖直方向),如果拖动下面的滑动条或者拖动上面的滑动条的处理方法。
在大的if语句中包含了一个if else语句,if表示的是左边的滑动条,else就是右边的滑动条;在大得else语句中也包含了一个if else语句,if表示的是竖直方向的上面的滑动条,else表示的是竖直方向的下面的滑动条。
理解了这一点就很容易定位到具体的滑动条的代码了。
先修改当手机处在竖直方向时(即滑动条处在水平方向)左边的解锁滑动条的距离,目标点的距离。
a) 将左边的滑动条tab向右边移动下
看上面紫红颜色的代码,将andleWidth加上一个数值即可,比如我们加上50变成:
tab.layout(0, top, handleWidth + 50, bottom);
那这几个参数是什么意思呢?
tab和target是ImageView对象,layout方法是继承android.view.View类,此方法表示给某个view进行布局,官网解释:
public void layout (int l, int t, int r, int b)
Assign a size and position to a view and all of its descendants
This is the second phase of the layout mechanism. (The first is measuring). In this phase, each parent calls layout on all of its children to position them. This is typically done using the child measurements that were stored in the measure pass().
Derived classes should not override this method. Derived classes with children should override onLayout. In that method, they should call layout on each of their children.
Parameters
Left position, relative to parent |
Top position, relative to parent |
Right position, relative to parent |
Bottom position, relative to parent |
由此可知,四个参数分别表示当前view相对于父视图的左、上、右、下的距离。
根据此可知,为了将tab向右移动一些距离,那可以将tab向右的距离增加一个数字即可,如下:
tab.layout(0, top, handleWidth + 50, bottom); // 这里加上了50,那么tab将会向右移动50个dp
按此推理,可以将target即目标点向左移动,如下:
target.layout(leftTarget, targetTop, leftTarget + targetWidth - 500, targetBottom); // 将向右的参数向减去500dp即表示向右的距离变少,那么目标点就会向左移动。
上面代码蓝色表示水平放置手机时(即滑动条处于垂直方向)的左边的tab的布局。青绿色的代码表示水平放置手机时(即滑动条处于垂直方向)的target的布局。
可以修改成:
tab.layout(left, parentHeight - handleHeight-50, right, parentHeight); // 减去50,表明将相对于父视图的top长度减少一个数值,这样tab的距离就会向下移动,就会变短。
target.layout(targetLeft, bottom+500, targetRight, bottom + targetHeight); // 加上500,表明将相对于父视图的top长度增加一个数值,这样target的距离就会向下移动,就会变短。
这里要注意,一会加上一个距离,一会减去一个距离,这时为什么?因为各个参数在layout里面表示的含义不一样,要具体看待,千万不能看这个参数的中文含义,比如说,你看到bottom+500,好像以为把底部的距离加上了500,那怎么看都想不通,都不是对的。所以要把参数带到具体的代码中去看,千万不能看这个单词的中文含义,容易迷惑,这一点我非常反感google,你可以把某个方法的名字起一个比较让人一看就懂这个方法是干什么的,但是不能把某个参数随便起名,容易迷惑。
修改保存后编译运行,发现target确实向左移动了,拖动滑动条开始解锁,这时发现可以解锁,但是解锁的临界点和target的位置不对应,那么如何解决?答:应该再减少拖动的距离,使得拖动一段距离后,正好和target相遇,此时正好解锁。如何修改?在SlidingTab类中还有一个方法:
public boolean onTouchEvent(MotionEvent event) {
if (mTracking) {
final int action = event.getAction();
final float x = event.getX();
final float y = event.getY();
switch (action) {
case MotionEvent.ACTION_MOVE:
if (withinView(x, y, this) ) {
moveHandle(x, y);
float position = isHorizontal() ? x : y;
float target = mThreshold * (isHorizontal() ? getWidth() : getHeight());
boolean thresholdReached;
if (isHorizontal()) {
thresholdReached = mCurrentSlider == mLeftSlider ?
position > target : position < target;
} else {
//xiehua
thresholdReached = mCurrentSlider == mLeftSlider ?
position < target : position > target;
}
if (!mTriggered && thresholdReached) {
mTriggered = true;
mTracking = false;
mCurrentSlider.setState(Slider.STATE_ACTIVE);
boolean isLeft = mCurrentSlider == mLeftSlider;
dispatchTriggerEvent(isLeft ?
OnTriggerListener.LEFT_HANDLE : OnTriggerListener.RIGHT_HANDLE);
startAnimating(isLeft ? mHoldLeftOnTransition : mHoldRightOnTransition);
setGrabbedState(OnTriggerListener.NO_HANDLE);
}
break;
}
// Intentionally fall through - we're outside tracking rectangle
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mTracking = false;
mTriggered = false;
mOtherSlider.show(true);
mCurrentSlider.reset(false);
mCurrentSlider.hideTarget();
mCurrentSlider = null;
mOtherSlider = null;
setGrabbedState(OnTriggerListener.NO_HANDLE);
break;
}
}
return mTracking || super.onTouchEvent(event);
}
最重要的是紫红色的代码(如上),他表示的含义是:
如果isHorizontal()返回为真,表示滑动条处于水平方向(注意,千万不能认为是手机处于水平方向,这一点很容易迷惑,很鄙视google,源代码也不叫上一个注释,害的我被迷惑了好几天)。
代码中得position表示滑动条移动的距离,position等于event.getX()。这时滑动条处在水平方向,为了减少滑动条向右解锁时移动的距离,那么就可以把position加上一个数值即可,那么加上一个多大的数值呢?这里为了使得解锁的临界点和target的位置在一个点上,必须满足一个等式:position = (500-50)/2 即position=225,为什么会这样呢?这个地方其实就是几何图形的长宽高的数值计算,target向左移动500,tab向右移动50,就等于target向左移动450,这时tab向右移动的距离大概等于450的一半,具体为什么是这样,其实我也是有点模糊,这个数值也是根据自己多次试验出来的,因为源代码中出现了很多有关长宽高的变量,没有注释,一时还真得难看懂,但这些数值试验后是正确的,所以大概推算就是这样的计算公式了(如果有非常了解的,还请告诉我一声)。
当手机处于垂直方向时,其实也是一个道理,加上和减去的数值都是一样的。
修改后的代码如下:
if (isHorizontal()) {
thresholdReached = mCurrentSlider == mLeftSlider ?
position + 225 > target : position < target;
} else {
//xiehua
thresholdReached = mCurrentSlider == mLeftSlider ?
position-225 < target : position > target;
}
注意上面,一个是加上225,一个是减去225,为什么?这个需要把数值带到具体代码中去看,同时需要画一个图形来看就清楚了。千万从变量的中文含义去理解,那就很容易迷惑。
至此,关于这个锁屏的修改基本上结束了。
关于锁屏还有其他的修改,比如把tab变成透明的,这个其实很简单,只要把tab的图片全部换成透明的就行了。
其他的修改还包括数字时钟、上下午时间显示等等,这些都是直接在下面的两个xml文件中修改:
frameworks/base/core/res/res/layout/keyguard_screen_tab_unlock_.xml
frameworks/base/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
这个修改不再详述。