[New learn] 手势
1.简介
我们经常会在设备上查看图片等, 也会经常将图片通过手指的捏合打开来缩小和方法图片。这就是ios中的手势功能在起作用。
那么手势好像也是一种touch事件,那和UIResponder中定义的touch事件有何关联呢?是不是同一个东西呢?这篇文章将会做解答。
2.手势与内置手势
在系统中,手势的基类为UIGestureRecognizer,而这个类继承自NSObject,内置手势总共六个,其中UIScreenEdgePanGestureRecognizer继承自
UIPanGestureRecognizer,其余都是继承自
UIGestureRecognizer。他们
分别是:
- UITapGestureRecognizer: 观察view上发生的轻敲手势。它可以处理单次或者多次的轻敲手势动作,不管是单点还是多点。轻敲手势是用户最常用的手势之一。
- UISwipeGestureRecognizer: 滑动手势是另外一个很重要的用户手势了,这个类就是为此而生的,当手指在屏幕上超一个方向(上下左右)滑动的时候既能被此类实例识别到。最常用的场景就是在相册应用中,我们经常通过他来切换照片。
- UIPanGestureRecognizer: 拖动手势,它经常被用于拖动view去不同位置。
- UIPinchGestureRecognizer: 当你在照片应用上浏览照片的时候你可能会经常使用两个手指进行放大和缩小,这个时候其实就是触发了捏合手势。正如你所理解的那样,捏合需要两个手指。此类的对象为我们提供方便的处理方法。使用捏合手势的例子有很多,你可以在你的应用中实现扩大和缩小照片的功能。
- UIRotationGestureRecognizer: 使用两个手指来对view进行旋转,比如旋转照片等。
- UILongPressGestureRecognizer:这个类的对象将监视发生在view上的长按手势。按下动作必须持续足够长的事件以使得此对象能够识别出,并且在持续按下的过程中手指不能滑动出离开按下点太多的距离。
- UIScreenEdgePanGestureRecognizer: 这个类很想滑动手势,但是他们两者的最大区别是:这个类手势只会识别从屏幕边上向内触发的滑动手势。
以上内置的手势类可以相互组合,如长按并拖动可以UILongPressGestureRecognizer+UIPanGestureRecognizer方式组合。
3.手势识别机制
测试代码:https://github.com/xufeng79x/GestureEventDelivery
在手势子类介绍中我们大致可以得到两个信息:
1.手势只作用在view上
2.手势与UIResponder之间没有关系。两者是相互独立的,手势并不是UIResponder的touch***处理方式的另外一种处理方式,应为手势类并没有集成UIResponder基类。
那么手势无论如何也是要手指摸才能相应的,并且是作用在一个UIView上的,而UIView又集成自UIResponder,那么当一个触摸事件传过来的时候手势和UIResponder的touch***处理方法之间是如何分辨到底有谁来处理的呢?
一般的当手势依附于某个UIView对象上的时候将会拦截本应该有此视图对象自行处理的触摸事件。可以将手势认为是一个UIView上的触摸事件的拦截器,由其先辨识到底此触摸是否符合当前view所持有的手势的特征,如果符合则讲出黎手势action,并不会讲此触摸事件继续往下传递。如果不符合则将继续交有该view的touch事件进行处理。
需要注意的是
1.【已证明,观点正确】触摸事件的被拦截是发生在手势已经成功被识别出后发生的,手势被识别出之前,view的touchBegan方法还是会接受到事件的。
2.【已证明,观点错误】从图中可以看出touch事件的寻主是找到view层级的最上层然后让这个最上层的view去相应事件,这个过程称之为hit-testing,初始处理事件的view为hit-test view。那么对于手势来讲
他将从UIApplication代理开始就不断的去拦截touch事件,换句话我们可以得出结论,父view通过手势被拦截的事件将不会在往子view中传。
证明一:1.触摸事件的被拦截是发生在手势已经成功被识别出后发生的,手势被识别出之前,view的touchBegan方法还是会接受到事件的。
为此我们新建一个工程加以测试:
a.新建工程,view如下图进行组合:
b.新建view子类,覆盖所有tuch方法,并将此view类作用用于storyborad生成的view2和view3上。
#import "TestView.h" @implementation TestView -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"in touchBegan of %@", self.name); } -(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"in touchesMoved of %@", self.name); } -(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"in touchesEnded of %@", self.name); } -(void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"in touchesCancelled of %@", self.name); } @end
c.在view3上增加双击手势,既增加轻敲手势设置的条件为2次,当辨识出手势后将调用action doubleTapHandleForView3去打印信息
- (void)viewDidLoad { [super viewDidLoad]; self.view2.name = @"view2"; self.view3.name = @"view3"; // view3为view层级的最顶层,在此view上增加一个双击手势 // 1.创建双击手势 UITapGestureRecognizer *doubleTapRecongnizer = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(doubleTapHandleForView3:)]; // 2.为这个tap手势设置条件,既连续轻敲两下会被识别到 doubleTapRecongnizer.numberOfTapsRequired = ; // 3.在指定view上加载此手势 [self.view3 addGestureRecognizer:doubleTapRecongnizer]; } // 当双击手势被识别后将处罚此方法 -(void)doubleTapHandleForView3:(UIGestureRecognizer *)gr { NSLog(@"I am in doubleTapHandleForView3"); }
d.运行程序,款速点击view3两次后结果控制台输出结果如下:
-- :::] in touchBegan of view3 -- :::] I am in doubleTapHandleForView3 -- :::] in touchesCancelled of view3
分析:可以看到touchBegan依然会被调用,原因为当我们首次点击view3的时候并不会认为是双击的轻敲手势,于是触摸事件依然会有touch**处理,当用户点击第二次的时候依附于view3的轻敲手势就被识别出用户行为,辨识出这是一种设定的手势,于是拦截并处理触摸事件。当手势执行完毕后view3继续能够接受和执行touch**方法。
如何避免:我们证明了此观点,那么我们如何没避免这种情况呢?这种情况一般会出现手势被识别前,有时候也会带来一些体验上的问题。一般地可以按照如下方法:
// 使得touchBegan方法延迟执行 doubleTapRecongnizer.delaysTouchesBegan = YES;
通过设定延迟此属性值让window延迟转发触摸事件给view,当在延迟期间手势识别到后,就不再向view转发触摸事件了。
运行双击view3后的结果:
-- :::] I am in doubleTapHandleForView3
总结:所以我们证明了这个观点,当手势识别是有一个事件过程和条件满足过程的,在未被识别之前还是会想view转发触摸事件,当被识别后将会贪婪的拦截所有触摸事件。
证明二:【将被证实为假】2.从图中可以看出touch事件的寻主是找到view层级的最上层然后让这个最上层的view去相应事件,这个过程称之为hit-testing,初始处理事件的view为hit-test view。那么对于手势来讲他将从UIApplication代理开始就不断的去拦截touch事件,换句话我们可以得出结论,父view通过手势被拦截的事件将不会在往子view中传。
a.hiting-view过程是会将触摸事件由application单例到window再到controller、父view一直传递到层级最上层的子View然后去处理的,而手势则会在传递过程中就拦截掉触摸事件,吗??!!
b.在上述例子中view2是view3的superview,我们在view2上也和view3一样增加同样的双击手势:
// 3.在指定view上加载此手势 [self.view3 addGestureRecognizer:doubleTapRecongnizer]; // view2为view3父view,我们为view2增加同view3一样的双击手势 // 1.创建双击手势 UITapGestureRecognizer *doubleTapRecongnizer1 = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(doubleTapHandleForView2:)]; // 2.为这个tap手势设置条件,既连续轻敲两下会被识别到 doubleTapRecongnizer1.numberOfTapsRequired = ; // 使得touchBegan方法延迟执行 doubleTapRecongnizer1.delaysTouchesBegan = YES; // 3.在指定view上加载此手势 [self.view2 addGestureRecognizer:doubleTapRecongnizer1]; }
c.增加view2的双击手势处理action:
// 当双击手势被识别后将处罚此方法 -(void)doubleTapHandleForView2:(UIGestureRecognizer *)gr { NSLog(@"I am in doubleTapHandleForView2"); }
d.运行程序,双击view3,预想应该输出“I am in doubleTapHandleForView2”,但是实际上:
-- :::] I am in doubleTapHandleForView3
总结1:所以我们得出原有观点错误,手势都会首先在hit-view上响应,如同触摸事件一样。
E.那么如果将view3的手势去掉,在运行应用会打印view3的touch**信息吗?我们来测试一下:
注释掉view3的手势:
// 使得touchBegan方法延迟执行 doubleTapRecongnizer.delaysTouchesBegan = YES; // 3.在指定view上加载此手势 //[self.view3 addGestureRecognizer:doubleTapRecongnizer]; // view2为view3父view,我们为view2增加同view3一样的双击手势 // 1.创建双击手势 UITapGestureRecognizer *doubleTapRecongnizer1 = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(doubleTapHandleForView2:)];
F.运行程序,双击view3,预想应该输出view3的touch**内的日志才对,但是实际上输出了父view的手势信息:
-- :::] I am in doubleTapHandleForView2
总结2:当当前view无手势,或者手势不识别的时候,将会向上去寻找父view的手势内容,知道便利完所有父view后都没有响应手势响应,则将执行当前view的touch**方法。
4.手势组合
代码:https://github.com/xufeng79x/GestureCombination
手势作用于UIView上,一个UIView对象可以加载多个手势,最为经典的莫过于长按后拖动操作。
a.首先创建工程GestureCombination,他被设计为有一个黑色的view,当长按于这个view的时候变为绿色,当长按并拖动的时候view会随之移动,当放开手指的时候view再次变为默认黑色。
b.为了达到上述目的,则需要两个手势:长按手势和拖动手势
- (void)viewDidLoad { [super viewDidLoad]; self.longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)]; // 长按是需要一个过程的,一般默认按下0.5秒后才能被识别出,为了避免在识别出之前做touchBegan事件,需要设定延迟转发 [self.longPressGesture setDelaysTouchesBegan:YES]; [self.testView addGestureRecognizer:self.longPressGesture]; self.panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panView:)]; [self.testView addGestureRecognizer:self.panGesture]; } //拖动事件处理 -(void) panView:(UIGestureRecognizer *)gr { NSLog(@"asfsaf"); } // 长按事件处理 -(void) longPress:(UIGestureRecognizer *)gr { // 当长按被识别到的时候,将view颜色设置为绿色 if (gr.state == UIGestureRecognizerStateBegan) { self.testView.backgroundColor = [UIColor greenColor]; } // 当长结束或者其他状态的时候设置为原来的黑色 else if (gr.state == UIGestureRecognizerStateChanged){ self.testView.backgroundColor = [UIColor greenColor]; } else{ self.testView.backgroundColor = [UIColor blackColor]; } }
我们按此完成代码后,发现当长按发生,并且view颜色也能够变成绿色,当长按并拖动的时候发现panView方法未被调用(日志为被打印出),换句话说拖动手势没有被识别,更为神奇的是touch**方法也没有被调用,这说明我们的触摸事件已经完全被长按手势给“贪婪的”拦截了。
c.在多手势组合中,当摸个手是被识别出后,他将拦截所有触摸事件,导致其他手势无法得到触摸事件而无法被识别。在本例中,长按后即使拖动,应为长按手势已经拦截了触摸事件,所以拖动就无法起效了。如何解决这个问题呢?
d.使用UIGestureRecognizerDelegate代理,将此代理赋值于拖动手势即可,同样我们无法获知源码,按照推断可以得出,其实拖动手势也已经在当前触摸事件中辨识出了属于自己的行为,但是多手势中默认禁止了这种行为,当我们覆盖如下方法,此方法相当于拖动手势识别出行为后询问【实现】我是否能够去做出处理。当次方法回答YES的时候,拖动手势就屁颠屁颠的干自己的活了。
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
在我的代码中具体代码为是:
@interface ViewController () <UIGestureRecognizerDelegate> 。。。。。。 self.panGesture.delegate = self; 。。。。。。 // 覆盖代理方法,当某个手势也辨识出当前行为时候,会调用此方法来申请是否能够参与活动 -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { if (gestureRecognizer == self.panGesture && otherGestureRecognizer == self.longPressGesture){ return YES; } return NO; }
此时我们在运行程序,长按view后,view将变为绿色,此时鼠标拖动,在panView:方法中日志将被打印,哈哈,这样我们就可以将日志打印代码替换掉换成自己的逻辑代码了。
e.实现拖动逻辑
//拖动事件处理 -(void) panView:(UIPanGestureRecognizer *)gr { // 在移动的时候触发处理 if (gr.state == UIGestureRecognizerStateChanged) { // 当前手势移动到哪里了 CGPoint translation = [gr translationInView:self.testView]; CGRect frame = self.testView.frame; frame.origin.x += translation.x; frame.origin.y += translation.y; self.testView.frame = frame; // 增加此行代码后,每一次都会传来增量的移动坐标数据。 [gr setTranslation:CGPointZero inView:self.testView]; } }
此时我们在测试一下我们的应用:
f.我以为我可以屁颠屁颠的去发布博客了,但是我错了,我遇到一个麻烦, 细心的读者可能会发现,即使没有长按让view变色,上手就拖动view也能够移动view,但是我们的设计是说需要在长按后才能拖动不是吗?所以我增加了一个长按标记位,然后在长按发生过程中设置为YES,其他状态设置为NO
// 手势限制 @property (nonatomic,assign) BOOL panEnable; 。。。。。 // 长按事件处理 -(void) longPress:(UILongPressGestureRecognizer *)gr { // 当长按被识别到的时候,将view颜色设置为绿色 if (gr.state == UIGestureRecognizerStateBegan) { self.testView.backgroundColor = [UIColor greenColor]; self.panEnable = YES; } // 当长按并移动手指的时候保持颜色不变 else if (gr.state == UIGestureRecognizerStateChanged){ self.testView.backgroundColor = [UIColor greenColor]; self.panEnable = YES; } // 当长结束或者其他状态的时候设置为原来的黑色 else{ self.testView.backgroundColor = [UIColor blackColor]; self.panEnable = NO; } } 。。。。
然后想当然的在拖动手势的代理方法中作出判断,如果此时标记位为YES则可以返回YES,具体的逻辑是这样的:
// 覆盖代理方法,当某个手势也辨识出当前行为时候,会调用此方法来申请是否能够参与活动 -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { if (gestureRecognizer == self.panGesture && self.panEnable){ return YES; } return NO; }
但是测试的结果出乎我的意料,不进行长按而直接能够拖动的问题依然存在。上述方法中,即使我没有逻辑,直接返货NO,问题也依然存在
或者可以说是“因祸得福”我得出了这个结论:
在多手势情况下,运用手势代理来进行多手势并行是需要条件的,既当前手势如果是第一手势(既直接第一个被辨识出的手势)的话,代理内的方法逻辑是不起效的。
所以此种情况下我使用了第二种方法来处理,既在拖动处理方法中做判断:
//拖动事件处理 -(void) panView:(UIPanGestureRecognizer *)gr { if (!self.panEnable) { return; }
5.手势冲突
手势冲突在多手势场景下也进场发生,如:
单机和双击轻敲组合
Swipe和Pan的组合等
解决这种场景我们可以使用手势
- requireGestureRecognizerToFail
方法来将几种手势穿起来,如
[轻敲一次手势 requireGestureRecognizerToFail: 轻敲两次手势]
这样就可以解决系统将轻敲两次手势辨识为两次轻敲一次手势了,系统运行的时候会优先去考两次轻敲两次是否成功,如果成功则不会再去辨识轻敲一次手势了,其他组合机制也是一样。
(我在想一个问题,为什么苹果不出一个叫做requireGestureRecognizerToOk)的方法,这样我长按拖动的问题不就解决了!!!!)
6.手势状态
任何一个手势都是有自身状态的,如最最基本的开始,结束,辨识失败等,对于连续性的手势如拖动,滑动,长按等还有changed的状态。
在代码逻辑中我们可以根据手势状态来进行一些逻辑上的处理,如很简单的我在第四小节F步骤中的标记位的设定等。
插卡接口代码,手势中有如下几种状态:
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) { UIGestureRecognizerStatePossible, // the recognizer has not yet recognized its gesture, but may be evaluating touch events. this is the default state UIGestureRecognizerStateBegan, // the recognizer has received touches recognized as the gesture. the action method will be called at the next turn of the run loop UIGestureRecognizerStateChanged, // the recognizer has received touches recognized as a change to the gesture. the action method will be called at the next turn of the run loop UIGestureRecognizerStateEnded, // the recognizer has received touches recognized as the end of the gesture. the action method will be called at the next turn of the run loop and the recognizer will be reset to UIGestureRecognizerStatePossible UIGestureRecognizerStateCancelled, // the recognizer has received touches resulting in the cancellation of the gesture. the action method will be called at the next turn of the run loop. the recognizer will be reset to UIGestureRecognizerStatePossible UIGestureRecognizerStateFailed, // the recognizer has received a touch sequence that can not be recognized as the gesture. the action method will not be called and the recognizer will be reset to UIGestureRecognizerStatePossible // Discrete Gestures – gesture recognizers that recognize a discrete event but do not report changes (for example, a tap) do not transition through the Began and Changed states and can not fail or be cancelled UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded // the recognizer has received touches recognized as the gesture. the action method will be called at the next turn of the run loop and the recognizer will be reset to UIGestureRecognizerStatePossible };
每一手势都运行在它的有限状态机中,从一种状态跳转(能够跳转)到另外一个状态都是预先定义好的。
同时对于手势来说又分为离散型和连续型,如tap就是离散型手势,捏合手势即使典型的连续型手势,从下面的方管我们可以得知,离散型对于注册的action是一次性的调用,而对于连续型手势则是连续不断地多次调用。
对于离散型和连续型手势去状态机也是不同的,遵从如下状态迁移:
上述左边既是离散型手势的状态机迁移图,对于离散型来说,手势要么被识别出,要么没有识别出。
上述右边是连续型的手势的状态机迁移图,对于连续型来说,手势从P状态一旦被识别到就进入B状态,随着手指的移动不断的刷新Chan状态,抬起手指时候则进入R状态(也就是ended状态),当有电话等系统时间打断的时候进入Can状态。
按照官方原话:
When a gesture recognizer reaches the Recognized (or Ended) state, it resets its state back to Possible. The transition back to Possible does not trigger an action message.
翻译为:当手势到达 Recognized(或者Ended)状态的时候,他将状态重新设定为Possible。状态机返回Possibale状态的时候不会触发action消息。
有此我们可以推断出其实Failed、Canceled和Possible一样都可以在状态机器中作为起始状态。
7.自定义手势
暂略。
[New learn] 手势的更多相关文章
- [转] 「指尖上的魔法」 - 谈谈 React Native 中的手势
http://gold.xitu.io/entry/55fa202960b28497519db23f React-Native是一款由Facebook开发并开源的框架,主要卖点是使用JavaScrip ...
- H5单页面手势滑屏切换原理
H5单页面手势滑屏切换是采用HTML5 触摸事件(Touch) 和 CSS3动画(Transform,Transition)来实现的,效果图如下所示,本文简单说一下其实现原理和主要思路. 1.实现原理 ...
- PhotoView实现图片随手势的放大缩小的效果
项目需求:在listView的条目中如果有图片,点击条目,实现图片的放大,并且图片可以根据手势来控制图片放大缩小的比例.类似于微信朋友圈中查看好友发布的照片所实现的效果. 思路是这样的:当点击条目的时 ...
- iOS7 NavigationController 手势问题
在iOS7中,如果使用了UINavigationController,那么系统自带的附加了一个从屏幕左边缘开始滑动可以实现pop的手势.但是,如果自定义了navigationItem的leftBarB ...
- [DeviceOne开发]-手势动画示例分享
一.简介 这是iOS下的效果,android下完全一致.通过do_GestureView组件和do_Animation组件,deviceone能很容易实现复杂的跨平台纯原生动画效果,这个示例就是通过手 ...
- iOS手势解锁、指纹解锁--Swift代码
一.手势密码 1. 1.1.用UIButton组成手势的节点. 1.2.当手指接触屏幕时,调用重写的 touchesBegan:withEvent方法(在touchesBegan里调用setNeeds ...
- ionic之$ionicGesture手势(大坑)
鄙人来本公司前未用过ionic框架,但由于ionic是基于angularjs封装的,正好我用过angularjs,很荣幸的面试就过了,然后通过该网站http://www.ionic.wang(后面简称 ...
- mui 手势事件配置
在开发中监听双击屏幕事件时不起作用,需要在mui.init方法的gestureConfig参数中设置需要监听的手势事件 手势事件配置: 根据使用频率,mui默认会监听部分手势事件,如点击.滑动事件:为 ...
- 超小Web手势库AlloyFinger原理
目前AlloyFinger作为腾讯手机QQ web手势解决方案,在各大项目中都发挥着作用. 感兴趣的同学可以去Github看看:https://github.com/AlloyTeam/AlloyFi ...
随机推荐
- 状态压缩---UVA6625 - Diagrams & Tableaux
比赛的时候刷出来的第一个状态DP.(期间有点没有把握是状态DP呢.) 题意:题意还是简单的.K行的方格.之后输入L1~LK 代表每一行方格数.在这些往左紧挨的方格子里填上1~N的数字. 其中右边格子的 ...
- 【刷题】BZOJ 2038 [2009国家集训队]小Z的袜子(hose)
Description 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿.终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命-- 具体来说,小Z把这N只 ...
- Android 职业路上--只要还有一丝希望,不到最后一刻,不要轻言放弃--从屌丝到进入名企
写在前面:只要还有一丝希望,不到最后一刻,不要轻言放弃! 来到西安十来天了,现在基本安顿下来了,这几天在工作中也遇到一些技术问题,但都没来得及总结分享,现在想和大家分享一下我的工作求职经历! 接触an ...
- Static全局变量与普通的全局变量有什么区别?static函数与普通函数有什么区别?
Static全局变量与普通的全局变量有什么区别? 答: 全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量.全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式. 这两者 ...
- BZOJ1179 [Apio2009]Atm 【tarjan缩点】
1179: [Apio2009]Atm Time Limit: 15 Sec Memory Limit: 162 MB Submit: 4048 Solved: 1762 [Submit][Sta ...
- SQL_MODE
一 声明 标红部分为重点了解 原文:https://segmentfault.com/a/1190000005936172 二 SQL_MODE参数值 官方手册专门有一节介绍 https://dev. ...
- JavaScript转换与解析JSON的方法
在JavaScript中将JSON的字符串解析成JSON数据格式,一般有两种方式: 一种为使用eval()函数. 使用Function对象来进行返回解析. 使用eval函数来解析,jquery的eac ...
- Nginx的配置文件简介及在Nginx中配置基于不同ip的虚拟主机
Nginx的配置文件简介及在Nginx中配置基于不同ip的虚拟主机: #user nobody; worker_processes 1; #error_log logs/error.log; #err ...
- 在CentOS 6.5 中安装JDK 1.7 + Eclipse并配置opencv的java开发环境(二)
一.安装JDK 1.7 1. 卸载OpenJDK rpm -qa | grep java rpm -e --nodeps java-1.6.0-openjdk-1.6.0.0-1.50.1.11.5. ...
- DOM动态增加控件
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <t ...