1、功能需求:表格中数据隔行变色、删除数据、全选删除、鼠标移动到图片时显示图片预览
2、示例代码如下:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD>
<TITLE> 数据管理 </TITLE>
<META NAME="Generator" CONTENT="EditPlus">
<META NAME="Author" CONTENT="">
<META NAME="Keywords" CONTENT="">
<META NAME="Description" CONTENT="">
<script language="javascript" type="text/javascript" src=/blog_article/"jquery-1.8.3.min.js">_br> </script>
<style type="text/css">
body{font-size:12px}
table{width:360px;border-collapse:collapse}
table tr th,td{border:solid 1px #666;text-align:center}
table tr td img{border:solid 1px #ccc;padding:3px;width:42px;height:60px;cursor:hand}
table tr td span{float:left;padding-left:12px}
table tr th{background-color:#ccc;height:32px}
.clsImg{position:absolute;border:solid 1px #ccc;padding:3px;width:85px;height:120px;background-color:#eee;display:none}
.btn{border:#666 1px solid;padding:2px;width:50px;filter:progid;DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=#ffffff,EndColorStr=#ECE9DB);}
</style>
<script type="text/javascript">
$(function(){
/**隔行变色**/
$("table tr:nth-child(odd)").css("background-color","#eee");
/**全选复选框单击事件**/
$("#chkAll").click(function(){
if(this.checked){
$("table tr td input[type=checkbox]").attr("checked",true);
}else{
$("table tr td input[type=checkbox]").attr("checked",false);
}
});
/**删除按钮单击事件**/
$("#btnDel").click(function(){
var intL = $("table tr td input:checked:not('#chkAll')").length;
if(intL !=0){
$("table tr td input[type=checkbox]:not('#chkAll')"). each(function(index){
if(this.checked){
$("table tr[id="+this.value+"]").remove();
}
});
}
});
/**小图片鼠标移动事件**/
var x = 5; var y = 15;
$("table tr td img").mousemove(function(e){
$("#imgTip").attr("src",this.src).css({"top":(e.pageY+y)+"px","left":(e.pageX+x)+"px"}).show(3000);
});
/**小图片鼠标移出事件**/
$("table tr td img").mouseout(function(){
$("#imgTip").hide();
});
});
</script>
</HEAD>
<BODY>
<table>
<tr>
<th>选项</th>
<th>编号</th>
<th>封面</th>
<th>购书人</th>
<th>性别</th>
<th>购书价</th>
</tr>
<tr id="0">
<td><input id="Checkbox1" type="checkbox" value="0"/></td>
<td>1001</td>
<td><img src=/blog_article/"Images/img03.jpg alt=""></td>
<td>刘明明</td>
<td>女</td>
<td>37.80元</td>
</tr>
<tr id="1">
<td><input id="Checkbox2" type="checkbox" value="1"/></td>
<td>1002</td>
<td><img src=/blog_article/"Images/img04.jpg alt=""></td>
<td>李小明</td>
<td>男</td>
<td>35.60元</td>
</tr>
<tr id="2">
<td><input id="Checkbox3" type="checkbox" value="2"/></td>
<td>1003</td>
<td><img src=/blog_article/"Images/img08.jpg alt=""></td>
<td>张小星</td>
<td>女</td>
<td>45.60元</td>
</tr>
</table>
<table>
<tr>
<td style="text-align:left;height:28px">
<span>
<input id="chkAll" type="checkbox"/>全选
</span>
<span>
<input id="btnDel" type="button" value="删除" class="btn"/>
</span>
</td>
</tr>
</table>
<img id="imgTip" class="clsImg" src=/blog_article/"Images/img03.gif> </BODY>
</HTML>
3、效果图预览:
在前一个系列文章中,我们从个体的角度来分析了Android应用程序窗口的实现框架。事实上,如果我们从整体的角度来看,Android应用程序窗口的实现要更复杂,因为它们的类型和作用不同,且会相互影响。在Android系统中,对系统中的所有窗口进行管理是窗口管理服务WindowManagerService的职责。在本文中,我们就将简要介绍WindowManagerService的职能以及制定学习计划。
我们知道,在Android系统中,同一时刻,只有一个Activity窗口是激活的,但是,对于WindowManagerService服务来说,这并不意味着它每次只需要管理一个Activity窗口,例如,在两个Activity窗口的切换过程中,前后两个Activity窗口实际上都是可见的。即使在只有一个Activity窗口是可见的时候,WindowManagerService服务仍然需要同时管理着多个窗口,这是因为可见的Activity窗口可能还会被设置了壁纸窗口(Wallpaper Winodw)或者弹出了子窗口(Sub Window),以及可能会出现状态栏(Status Bar)以及输入法窗口(Input Method Window),如图1所示。
图1 Activity窗口及其子窗口、壁纸窗口、输入法窗口和状态栏的位置结构
因此,WindowManagerService服务是不可以假设同一时刻它只需要管理一个窗口的,它需要通过各个窗口在屏幕上的位置以及大小来决定哪些窗口需要显示的以及要显在哪里,这实际上就是要计算出各个窗口的可见区域。
从前面Android系统Surface机制的SurfaceFlinger服务渲染应用程序UI的过程分析一文可以知道,SurfaceFlinger服务在渲染整个屏幕的UI的时候,会对各个窗品的可见性进行计算,因此,WindowManagerService服务只要将它所管理的各个窗品的位置以及大小告诉SurfaceFlinger服务,后者可以帮帮它计算出各个窗口的可见区域了。注意,这里,这里所说的窗口位置包括窗口在X、Y和Z轴的位置。
WindowManagerService服务大致按照以下方式来控制哪些窗口需要显示的以及要显在哪里:
1. 每一个Activity窗口的大小都等于屏幕的大小,因此,只要对每一个Activity窗口设置一个不同的Z轴位置,然后就可以使得位于最上面的,即当前被激活的Activity窗口,才是可见的。
2. 每一个子窗口的Z轴位置都比它的父窗口大,但是大小要比父窗口小,这时候Activity窗口及其所弹出的子窗口都可以同时显示出来。
3. 对于非全屏Activity窗口来说,它会在屏幕的上方留出一块区域,用来显示状态栏。这块留出来的区域称对于屏幕来说,称为装饰区(decoration),而对于Activity窗口来说,称为内容边衬区(Content Inset)。
4. 输入法窗口只有在需要的时候才会出现,它同样是出现在屏幕的装饰区或者说Activity窗口的内容边衬区的。
5. 对于壁纸窗口,它出现需要壁纸的Activity窗口的下方,这时候要求Activity窗口是半透明的,这样就可以将它后面的壁纸窗口一同显示出来。
6. 两个Activity窗口在切换过程,实际上就是前一个窗口显示退出动画而后一个窗口显示开始动画的过程,而在动画的显示过程,窗口的大小会有一个变化的过程,这样就导致前后两个Activity窗口的大小不再都等于屏幕的大小,因而它们就有可能同时都处于可见的状态。事实上,Activity窗口的切换过程是相当复杂的,因为即将要显示的Activity窗口可能还会被设置一个启动窗口(Starting Window)。一个被设置了启动窗口的Activity窗口要等到它的启动窗口显示了之后才可以显示出来。
从以上六点就可以看出,窗口在X、Y和Z轴的位置及其大小的计算非常重要,它们共同决定了一个窗口是否是整体可见的,还是部分可见的,或者整体不可见的。在Android系统中,WindowManagerService服务是通过一个实现了WindowManagerPolicy接口的策略类来计算一个窗口的位置和大小的。例如,在Phone平台上,这个策略类就是PhoneWindowManager。这样做的好处就是对于不同的平台实现不同的策略类来达到不同的窗口控制模式。
从上面的描述就可以看出,WindowManagerService服务除了要与Activity窗口所运行在的应用程序进程打交道之外,还需要与SurfaceFlinger服务以及窗口管理策略类PhoneWindowManager交互,如图2所示。
图2 WindowManagerService服务与Activity窗口、SurfaceFlinger服务、PhoneWindowManager策略的关系图
在前面Android应用程序窗口(Activity)实现框架简要介绍和学习计划的一系列文章中,我们已经分析过应用程序进程与WindowManagerService服务之间的交互过程了,因此,在这一系列文章中,我们就将主要分析WindowManagerService服务的实现,以及它与SurfaceFlinger服务、PhoneWindowManager策略类的交互过程。
从总体上来看,WindowManagerService服务的实现是相当复杂的,例如,WindowManagerService类的核心成员函数performLayoutAndPlaceSurfacesLockedInner的代码有1200+行,比600-行代码的ViewRoot类的核心成员函数performTraversals还要恐怖。不过,WindowManagerService服务实现的复杂性是在预料之中的,毕竟它要管理的整个系统所有窗口的UI,而在任何一个系统中,窗口管理子系统都是极其复杂的。基于上述理由,采用硬碰硬的方式来分析WindowManagerService服务的实现是以卵击石,因此,这个系列的文章将对WindowManagerService服务进行分拆,然后再逐个击破,这是算法中的分而治之思想是一致的。
具体来说,我们将按照以下几个情景来分析WindowManagerService服务的实现:
1. 窗口大小和位置(X轴和Y轴)的计算过程。
2. 输入法窗口的调整过程。
3. 壁纸窗口的调整过程。
4. 窗口Z轴位置的计算和调整过程。
5. Activity窗口的启动窗口的显示过程。
6. Activity窗口的切换过程。
再次地,由于WindowManagerService服务的实现实在是太复杂,因此上述六个情景可能还不足于说明WindowManagerService服务的实现。如果出现这种情况,我们在分析的过程中会进行相应的调整。相信对WindowManagerService服务的实现进行分而治之的分析后,我们就可以对Android系统的UI架构有一个深刻的理解!敬请关注接下来的文章!
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!
说明:设计模式系列是yqj2065为《编程导论》准备的扩展讲义,比较粗俗。也不准备采用《设计模式》的那一套搞法。比较遵循《设计模式》的讲解,可以参考刘伟的相关文章和书籍。
在[编程导论·4.2.3接口继承Vs. 实现继承]中,强调了接口继承(协议继承)和抽象方法的重要性。然而,实现继承——复用父类的代码的能力仍然非常重要。子类对待父类代码有5种形式:直接继承、改进语意的改写、替换语意的改写、对空方法的改写、对抽象方法的继承。仅前2种有效地复用了父类代码,后2种情况父类没有提供代码。而替换语意的改写体现了对父类方法的漠视。
正如Java 禁止goto语句但是提供受限变体break和continue一样,模板方法模式研究如何有效地控制和合理地使用实现继承。
受限制的实现继承
代码向上(父类)集中,而子类直接继承是理想的设计方案。但是,父类的代码中因为有一些片段是不确定的/可变的,因而才使得子类要override父类的方法。
public class Sup{
public void foo(){
{/*step1*/}
{/*step2*/}
{/*step3*/}
}
}
例如父类Sup的方法foo(),假定其中的块语句step2有多种变化。如果按照foo()的实现方案,Sup的子类就不得不用替换语意的改写。一种简单的改善方案时,将块语句分别设计成方法,代码如下:
package 模板方法模式;
import static tips.Print.*;
public abstract class Sup{
public final void step1(){ pln("step1"); }
public abstract void step2(); //public void step2(){}
public final void step3(){pln("step3"); }
public void templateMethod(){
step1();step2();step3();
}
}
此时,方法foo()变成了称之为模板方法的templateMethod()。模板方法定义了一个过程/方法的执行流程(算法的骨架),而某一些可变化的步骤由子类实现。templateMethod()中调用的方法被称为基本操作(primitive operations)。
模板方法模式将整个流程中不变的部分和可变的部分有效分开后(这是前提),模板方法定义执行流程,不变的部分由父类提供(可复用的)代码,可变的部分由子类延迟实现。
钩子方法?
钩子(hook)是(用C++等)Windows编程中常用的术语。Windows消息处理机制中,如果发生了某种事件,一般由目标窗口的处理函数处理它。钩子(hook)本质上是一个回调函数,当自己或其它进程发生了某种事件,应用程序所定义的钩子先捕获该消息(先得到控制权),它可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。钩子使得用户程序可以对操作系统进行控制。
类层次中,子类可以调用父类的方法,但是父类不能够调用子类的方法。参考[编程导论·9.3.1回调],子类相当于父类的上层模块(虽然有一些别扭),下层模块(父类)要调用上层模块的某个方法f(),需要将f()设计成回调并将f()放在下层模块能够访问的父类型中。这时,作为下层模块的父类正好能够访问自己(下层模块和公用模块是一个模块)。很大程度上,《设计模式》中使用hook op这个术语纯属找抽,除了装B之外,没有任何价值。
虽然子类可以进行改进语意的改写、替换语意的改写、对空方法的改写,而前两者是需要避免的,抛开对操作系统的控制 (当然操作系统愿意开放了其Hook API让程序员编写回调)、回调的所有含义,钩子在本模式中,“应该”意味着父类定义的空方法。(如果在本模式中钩子仅仅表示可被改写的方法,连空方法都不独家代理的话,钩子就是一个麻袋,装啊装啊装)
1.父类强制子类遵循不变的结构
如果要求子类遵循不变的结构,只需要将templateMethod()定义为final即可。子类不能够改写templateMethod(),因而不得不按照模板方法定义的执行流程(算法的骨架)运行。
2.给子类对执行结构的选择权
public void templateMethod(){
step1();
if( isExe2() ) step2();
if( isExe3() ) step3();
}
public abstract boolean isExe2();//强制必须选择
public boolean isExe3(){//自由选择
return true ;
}
所以,钩子不钩子没有任何意义。isExe2()是abstract方法,它是钩子吗?isExe3