[转载]指尖下的js ——多触式web前端开发之一:对于Touch的处理 - pifoo - 博客园

mikel阅读(1065)

[转载]指尖下的js ——多触式web前端开发之一:对于Touch的处理 – pifoo – 博客园.

水果公司的那些small and cute的设备给我们提供了前所未有的用户体验。当用户在iphone和ipad上运指如飞
的时候,那些使用objective-c写出优秀应用的开发人员们心里一定充满了成就感,因为正是他们(而不是水果
)让iOS的世界变的丰富多彩。然而对于我们这些以web为自己事业核心的程序员来说,这种让人欲罢不能的多
触式体验似乎跟我们关系不大,因为浏览器那一小块地方就是我们和用户的全部交集了。而许多网站为了让iOS
的用户能够在多触式体验下使用自己的服务,都专门花钱开发objective-c的本地程序作为自己web service的客
户端。
其实,对于一个web程序员或者一个网站来说,如果你的需求仅仅是让iPhone/iPad用户能够正常使用你的
服务,那现有的html4页面完全能够满足(也许需要一点点重构,但是很容易);如果再往上一点,你需要让你
的web客户端看起来像是用objective-c实现的一样,也并非不可能,只需要将我们熟悉的JavaScript搬到iOS设
备上来就行。
本文从一个多触式网页开发初学者的角度,首先简单介绍一下iOS上的浏览器(这里主要指Safari)所支持的
多触式事件模型,然后将触控(Touch)这种简单的动作升级为手势(Gestrue),最后将JavaScript + html +
css构建的应用脱离浏览器,放到iOS设备的屏幕上成为一个本地link并和植物大战僵尸放到一起。
iOS上的Safari也支持click 和mouSEOver等传统的交互事件,只是不推荐在iOS的浏览器应用上使用click和
mouSEOver,因为这两个事件是为了支持鼠标点击而设计出来的。Click事件在iOS上会有半秒左右的延迟,原
因是iOS要highlight接收到click的element。而mouSEOver/out等事件则会被手指的点击触发。所以,在iOS上
,应当抛弃传统的交互事件模型而接受一个新的事件模型。Touch事件和更高级的Gesture事件,能让你的网页
交互起来像native应用一样。
处理Touch事件能让你跟踪用户的每一根手指的位置。你可以绑定以下四种Touch事件:

touchstart:  // 手指放到屏幕上的时候触发
touchmove:  // 手指在屏幕上移动的时候触发
touchend:  // 手指从屏幕上拿起的时候触发
touchcancel:  // 系统取消touch事件的时候触发。至于系统什么时候会取消,不详。。

Gesture事件则是对touch事件的更高级的封装,主要处理手指slide、rotate、scale等动作,将在下一篇文章
详述。
在开始描述touch事件之前,需要先描述一下多触式系统中特有的touch对象(Android和iOS乃至nokia最新
的meego系统都模拟了类似的对象,这里只针对iOS,因为我只有iPad可用于测试。。)。这个对象封装一次
屏幕触摸,一般来自于手指。它在touch事件触发的时候产生,可以通过touch event handler的event对象取到
(一般是通过event.changedTouches属性)。这个对象包括一些重要的属性:

client / clientY:// 触摸点相对于浏览器窗口viewport的位置
pageX / pageY:// 触摸点相对于页面的位置
screenX /screenY:// 触摸点相对于屏幕的位置
identifier: // touch对象的unique ID

CSS代码

.spirit {              /* 方块的class名称*/
         position: absolute;
         width: 50px;
         height: 50px;
         background-color: red;
}

然后,在body下定义一个接收事件的容器,这里body的height必须是100%才能占满整个viewport:

Html

<body style=”height: 100%;margin:0;padding:0”>

<div id=”canvas”  style=”position: relative;width:100%;height: 100%;”></div>

</body>

定义touchstart的事件处理函数,并绑定事件:

Javascript代码

// define global variable

var canvas = document.getElementById(“canvas”),
       spirit, startX, startY;
    // touch start listener

function touchStart(event) {
         event.preventDefault();
         if (spirit ||! event.touches.length) return;
         var touch = event.touches[0];
         startX = touch.pageX;
         startY = touch.pageY;
         spirit = document.createElement(“div”);
         spirit.className = “spirit”;
         spirit.style.left = startX;
         spirit.style.top = startY;
         canvas.appendChild(spirit);
}

// add touch start listener
canvas.addEventListener(“touchstart”, touchStart, false);

首先,我们将方块spirit作为一个全局对象,因为我们现在要测试单根手指所以屏幕上最好只有一个物体在移动

(等会有多触实例)。在touchStart这个事件处理函数中,我们也首先判断了是否已经产生了spirit,也就是是
否已经有一个手指放到屏幕上,如果是,直接返回。
和传统的event listener一样,多触式系统也会产生一个event对象,只不过这个对象要多出一些属性,比如
这里的event.touches,这个数组对象获得屏幕上所有的touch。注意这里的event.preventDefault(),在传统的
事件处理函数中,这个方法阻止事件的默认动作,触摸事件的默认动作是滚屏,我们不想屏幕动来动去的,所
以先调用一下这个函数。我们取第一个touch,将其pageX/Y作为spirit创建时的初始位置。接下来,我们创建
一个div,并且设置className,left,top三个属性。最后,我们把spirit对象appendChild到容器中。这样,
当第一根手指放下的时候,一个红色的,50px见方的方块就放到屏幕上了。
然后,我们要开始处理手指在屏幕上移动的事件:

Javascript代码

function touchMove(event) {
         event.preventDefault();
         if (!spirit || !event.touches.length) return;
         var touch = event.touches[0],
              x = touch.pageX – startX,
              y = touch.pageY – startY;
         spirit.style.webkitTransform = 'translate(' + x + 'px, ' + y + 'px)';     
}

Canvas.addEventListener(“touchmove”, touchMove, false);

在touch move listener中,我们使用webkit特有的css属性:webkitTransform来移动方块,这个属性具体怎么用请google之。建议构造 面向iOS设备的网页的时候尽量使用webkit自己的特性,不但炫,更可以直接利用硬件来提高性能。

最后,我们处理touchend事件。手指提起的时候方块从屏幕上移除。

function touchEnd(event) {
         If (!spirit) return;
         canvas.removeChild(spirit);
         spirit = null;
}

canvas.addEventListener(“touchend”, touchEnd, false);

在你的ipad或者iphone上测试一下以上代码。如果不出意外的话,一个完整的多触式web程序就诞生了。。
这种事件处理模式虽然能够满足我们开发多触式网页应用的需求,但是start – move – end的流程有点繁琐,
能不能封装一些常用的动作让我们用一个event handler就能解决问题呢。没错,Gesture事件就是为了这个目
的设计出来的,它封装了手指的scale, slide, rotate等常用的动作。不过,下一章我们再来讨论这个问题。。
附件是一个更加复杂一些的例子,每根手指放下的时候都会产生一个不同颜色的方块,手指动的时候方块跟着
动,手指提起的时候方块消失,请下载查看试用。
通过附件所包含的实例,我们可以看出一些较为隐蔽的特性。首先,这里我们没有再使用event.touches取所有
touch的对象,而是使用event.changedTouches这个数组,用来取得所有跟本次事件相关的touch。但是,这
里我发现一个奇怪的特性,不知道是我的ipad有问题还是本来就是这样,就是在有多根手指放在屏幕上的时候
,如果提起一根手指,touchend事件的changedTouches中会包含所有手指的touch对象,然后,其他几根留
在屏幕上的手指会重新触发touchstart,并刷新所有的touch对象(identifier都不一样了)。如果这是一个所有
设备都有的特性,那么将给编程者带来一些不便,不知道水果为啥要这么处理。
对touch event的介绍我们点到为止,这里给大家推荐两篇文档:
1. Safari dom additions reference
2. Safari web content guide
对于有志于开发多触式网页应用的程序员来说,apple的developer站点是一个应该经常光顾的地方。

附件:ios-multi.rar

[转载]Android百度地图开发(二)地图覆盖物 - 若。只如初见 - 博客园

mikel阅读(1076)

[转载]Android百度地图开发(二)地图覆盖物 – 若。只如初见 – 博客园.

上一篇文章写道如何在一个mapview中简单的显示地图;本次学习一下如何在地图上添加一些覆盖物。

1.设置显示交通地图:

1 // 显示交通地图
2 mapView.setTraffic(true);

2.设置显示为卫星地图:

// 显示交通地图
//mapView.setTraffic(true);

// 显示卫星地图
mapView.setSatellite(true);

当然卫星地图和交通地图也可以同时显示

3.百度地图提API供了一下几种覆盖物

// MyLocationOverlay:负责显示用户当前位置
// PoiOverlay:本地搜索图层,提供某一特定区域的位置搜索服务
// RouteOverlay:不行、驾车导航线路图层,将步行、驾车出行方案的线路及关键点显示在地图上。
// TransitOverlay:公交换乘路线图层,将某一特定区域的公交出行方案的线路及换乘位置显示在地图上 

4.给一个覆盖物添加一些标注

复制代码
    public class MyOverlay extends Overlay {

        // 用来显示当前的经纬度
        private GeoPoint geoPoint = new GeoPoint((int) (39.915 * 1E6),
                (int) (116.404 * 1E6));

        //声明一个画笔
        private Paint paint = new Paint();

        @Override
        public void draw(Canvas arg0, MapView arg1, boolean arg2) {
            // TODO Auto-generated method stub
            super.draw(arg0, arg1, arg2);

            Point point = mapView.getProjection().toPixels(geoPoint, null);
            arg0.drawText("*这里是天安门*", point.x, point.y, paint);
        }

    }
复制代码

然后在MainActivity中将这个点添加进去

mapView.getOverlays().add(new MyOverlay());

5.给多个覆盖物添加标注

复制代码
    @SuppressLint("NewApi")
    public class MyOverlayItem extends ItemizedOverlay<OverlayItem> {

        private List<OverlayItem> list = new ArrayList<OverlayItem>();
        // 定义一个坐标,double类型
        private double mLat1 = 39.90923; // 表示经度
        private double mLon1 = 116.397428;// 表示纬度

        private double mLat2 = 39.9022; // 表示经度
        private double mLon2 = 116.3922;// 表示纬度

        private double mLat3 = 39.917723; // 表示经度
        private double mLon3 = 116.37222;// 表示纬度

        // 用于在地图上标识坐标,用一个图片标注
        public MyOverlayItem(Drawable arg0) {
            super(arg0);
            // TODO Auto-generated constructor stub

            GeoPoint geoPoint1 = new GeoPoint((int) (mLat1 * 1E6),
                    (int) (mLon1 * 1E6));

            GeoPoint geoPoint2 = new GeoPoint((int) (mLat2 * 1E6),
                    (int) (mLon2 * 1E6));

            GeoPoint geoPoint3 = new GeoPoint((int) (mLat3 * 1E6),
                    (int) (mLon3 * 1E6));

            // 分别在list中把这几个点添加进去
            list.add(new OverlayItem(geoPoint1, "Point01", "Point01"));
            list.add(new OverlayItem(geoPoint2, "Point02", "Point02"));
            list.add(new OverlayItem(geoPoint3, "Point03", "Point03"));

            //地图刷新的功能
            populate();

        }

        // 返回制定的list集合中每一个坐标
        @Override
        protected OverlayItem createItem(int arg0) {
            // TODO Auto-generated method stub
            return list.get(arg0);
        }

        // 返回地图上点的个数
        @Override
        public int size() {
            // TODO Auto-generated method stub
            return list.size();
        }

         @Override
         public boolean onTap(int i) {
         // TODO Auto-generated method stub
         Toast.makeText(MainActivity.this, list.get(i).getSnippet(),
         1).show();
         return true;
         }

    }
复制代码

然后在MainActivity中添加一下代码(需要在项目中导入一张图片,命名为:iconmarka):

        Drawable drawable = getResources().getDrawable(R.drawable.iconmarka);
        mapView.getOverlays().add(new MyOverlayItem(drawable));

最终效果图:

 

[转载]web上渐进使用jQuery Mobile中animate相关CSS « 张鑫旭-鑫空间-鑫生活

mikel阅读(1092)

[转载]web上渐进使用jQuery Mobile中animate相关CSS « 张鑫旭-鑫空间-鑫生活.

by zhangxinxu from http://www.zhangxinxu.com
本文地址:http://www.zhangxinxu.com/wordpress/?p=2748

一、关于animate.css

在介绍主人公之前,先说说他的亲戚。

有个叫“蛋一灯(Dan Eden)”的人弄了个名叫animate.css的开源项目,实际上就是使用CSS3 animation实现了各种蛋疼或菊紧的动画效果。

官方主页地址:http://daneden.me/animate
github地址:https://github.com/daneden/animate.css

该项目不错之处在于,你可以自定义你自己需要的CSS效果,自定义页面访问点击这里

自定义你需要的动画效果 张鑫旭-鑫空间-鑫生活

在目标浏览器下,鼠标移到文字上即可预览到效果,勾选与去勾选,点击最下面的”Build it”按钮,可以生成你自己需要的效果的CSS文件。如下示意:
生成自定义的animate.css动画文件 张鑫旭-鑫空间-鑫生活

然而,从实际的角度来讲,这个项目基本上属于华丽包装的中国式礼品,除了让我们学习如何写效果相对应的CSS3代码。换句话说,有技术学习的价值,缺少实际应用的价值!因此,本文的主人公不是他,而是她!

二、JQuery Mobile中animate.css

jQuery Mobile中也有animate相关的CSS代码(没有animate.css之类名称,标题这么写是是为了与前一个标题呼应)。

虽然同是animation相关动画的CSS代码,但是,JQuery Mobile中的这个显然更简单更实用,更值得一说!

我抽了点拉便便的功夫,把JQuery Mobile项目中的这部分CSS代码提出来了,放在了一个独立的CSS文件中(有改动,兼容、命名、属性等),您可以狠狠地点击这里查看或下载:animate.css

其中,相关animation动画有:spin, fade, pop, slide, flip, turn, flow. 至于具体分别指代什么,下面会讲,稍安勿躁!

有了animate.css, 实现一些动画效果那是非常的简单——几个class类名的切换而已,就算是不懂JavaScript的人,松松的几十分钟,也可以弄出效果来。

对于大多数同行,虽未实践过animation动画,但肯定也有所耳闻,这些CSS3属性IE6~9甚至Opera浏览器都不支持,顶多手机项目或者iPad项目上用用,至于传统Web上,啧啧,估计吃翔的可能性居多!

所以,可能“米娜桑”现在对于animation等东西更多的是观望,了解或等待之类!No, no, no, 诸位,今天我就要告诉大家,就算是需要支持IE6浏览器,面向各类普通用户的传统web项目,animate也是可以渐进使用的,而且使用成本相当相当的低的哦!哟,还不信,咱们骑驴看唱本——走着瞧!

注解:① 大家的意思。

三、 热热身 – slide相关CSS与幻灯片切换效果

面对新事物,鲜活的实例远比生僻头大的代码、陈述之类更加吸引人。

您可以狠狠地点击这里:slide动画与幻灯片切换浏览效果demo

在Chrome浏览器或者FireFox浏览器下(或360,遨游,搜狗浏览器的极速模式),点击图片,您会看到图片们向左不停地、以流畅动画形式,显示啊显示啊显示……
slide效果在FireFox浏览器下的截图 张鑫旭-鑫空间-鑫生活

在不知CSS3为何物的浏览器下,例如IE7这厮,图片也会一个一个切换,只是木有动画效果而已——对于实际应用而言,足够了!!——我们平时的效果基本上就是这样的,FireFox等浏览器更加better而已!

实现
要说如何实现的,咳咳,说穿了真是简单地让人吐血。

  1. 显然的,调用我提出并编辑过的animate.css文件,如下代码:
    <link rel="stylesheet" href="http://www.zhangxinxu.com/study/css/animate.css" type="text/css" />
  2. 给要动画的元素添加几个关键的类名,例如这里是slide效果,因此加一个名叫slide的类名,如下截图:
    添加关键的类名slide示意图 张鑫旭-鑫空间-鑫生活
  3. 下面就是JS把inout两个类名切换切换就结束了!
    JS切换in和out类名实现animation动画效果 张鑫旭-鑫空间-鑫生活

好了,可以去吃晚饭了 –

四、animate.css回锅再炒

上面的例子作用有2个:1. 提起兴趣;2. 大致认识。

于是,现在,到了可以好好讲讲animate.css相关内容了:

  • animate.css驱动下的各种动画效果都是通过切换类名实现的;
  • 类名分为三类:公用类名、动画关键字类名以及可有可无的两个3D视角类名。
    公共类名有3个:in, outreverse. 分别指无到有、有到无、反向。
    关键字类名9个:fade, pop, slide, slidefade, slidedown, slideup, flip, turn, flow. 各个效果后面有展示;
    3D视角类名2个:viewport-flip, viewport-turn. 从名字就可以看出,是flip效果和turn效果需要的。
  • 虽说IE10也会支持animation动画,但是,这里只有moz, webkit前缀驱动,因此,IE10下无效果(您自然可以添加更多CSS使IE10以及后续的Opera浏览器支持)。
    仅有moz和webkit前缀截图示意 张鑫旭-鑫空间-鑫生活
    从实际应用的角度讲,为了可以准确判断向下不支持的浏览器,这样的命名是比较推荐的。但是,5年之后,必然,这里的命名等需要大动。
  • flip效果和turn效果属于3D变幻的范畴,因此父级元素上有必要设置:
    perspective: 1000px

    您可以使用animate.css中的viewport-flip, viewport-turn或者使用自己定义的类名。因此,我说对于animate.css而言,viewport-flip, viewport-turn不是必须的。

  • animate.css中的每一行的CSS代码都是比较高级的CSS3属性,因此包括IE9在内的浏览器都是根本不认识的。这种完全不认识性,使得我们的兼容性处理就变得相当简单了。

in, out, reverse类名的理解
各种动画效果的实现的本质就是“使用JavaScript对in, out, reverse三个类名颠来倒去切换”。

一般而言,in表示元素从看不见到出现的动画效果。例如fade + in的动画效果就是淡入(图片透明度从0到1)。而out指代元素隐藏,逝去,例如fade + out的动画效果就是淡出(图片透明度从1到0)。

类似的slide+in效果就是移入,slide+out效果就是移出;pop+in效果就是弹出;pop+out效果就是收进去;等等!

reverse的作用是反向。举个例子,最简单的slide效果:进来是slide+in,即从右往左。如果移出是slide+out则还是从右往左移出,如果移出是slide+reverse+out,则是从左往右移出,也就是原路返回

因此,reverse一般用在独立元素的交互效果上,例如弹框出现和弹框关闭的效果应该是完全相反的,这时候就需要用到类名reverse.

在以前的jQuery版本中,in, out动画的时间都是一样的,如下代码:

.in, .out {
    -webkit-animation-timing-function: ease-in-out;
    -webkit-animation-duration: 350ms;
}

不过现在做了不同处理,默认动画进入350毫秒,动画移出225毫秒。至于为什么做这番修改,我也不得而知,总之对相关并木有什么糟糕的影响,我们仍可从容使用之。

五、animate.css的向下兼容

前面说过,包括IE9, Opera浏览器在内的浏览器都是不支持animate.css的动画CSS的,如果保证这些浏览器的显示也是正常的。

其实很简单,只要让这些浏览器有下面这一行CSS代码就可以了:

.out { display: none!important; }

animate.cssout类名的本质就是以动画形式让元素隐藏(不可见);其本质与直接的元素隐藏(display:none)是一样的。

然后,什么in, out之类的切换就完全不会影响在低版本浏览器上的显示了。

下面问题来了,如何让非目标浏览器上渲染display:none呢?

考虑到CSS hack太难搞,@supports目前仅FireFox17支持,我是借助JavaScript实现的,完整代码如下:

var BROWSER = function() {
    var ua = navigator.userAgent.toLowerCase();

    var match = /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
        /(opera)(?:.*version)?[ \/]([\w.]+)/.exec( ua ) ||
        /(msie) ([\w.]+)/.exec( ua ) ||
        !/compatible/.test( ua ) && /(mozilla)(?:.*? rv:([\w.]+))?/.exec( ua ) ||
        [];

    return { browser: match[1] || "", version: match[2] || "0" };
}();

if ((BROWSER.animate = (BROWSER.browser !== "mozilla" && BROWSER.browser !== "webkit"))) {
    // 不是目标浏览器,创建CSS向下兼容
    var oStyle = document.createElement("style"), cssText = ".out{display:none!important;}";
    oStyle.type = "text/css";
    if (BROWSER.browser === "msie") {
        oStyle.styleSheet.cssText = cssText;
    } else {
        oStyle.innerHTML = cssText;
    }    
    document.getElementsByTagName("head")[0].appendChild(oStyle);
}

建议代码放在页面的头部,或者在头部放入如下的JS文件链接代码:

<script src="http://www.zhangxinxu.com/study/201210/animate-fix.js"></script>

如果您的头部已经链接了例如jQuery框架,更简单,直接(未测试):

if (!$.browser.webkit && !$.browser.mozilla) $("head").append('<style>.out{display:none!important;}</style>');

六、fade以及slidefade动画体验

fade动画效果
fade效果可以说是最好理解,最易识别的效果了。淡入淡出效果为jQuery内置动画效果,如果两者画个等号的话,类似这样:

$().fadeIn() = $().addClass("fade in");
$().fadeOut() = $().addClass("fade out");

您可以狠狠地点击这里:fade动画下的图片轮播效果demo

// zxx: 下面的N多demo中也夹杂着fade效果,同时fade动画是唯一没有reverse参与的动画类型。

slidefade动画效果
slidefadeslide移动和fade淡入淡出效果的结合,您可以狠狠地点击这里:slidefade动画下的图片轮播效果demo

slidefade效果进行中的截图 张鑫旭-鑫空间-鑫生活

到目前为止展示的三个demo,唯一不同的就是HTML代码中的类名:
类名驱动的animation动画效果 张鑫旭-鑫空间-鑫生活

可见,CSS3 animate.css下的动画效果完全由类名驱动的。

对于多元素且有规律的动画效果,一般关键类名reverse是不参与进来的。

但是,对于单独的元素动画,reverse就不可或缺了。

七、slideup动画效果展示

这里展示slideup动画,同时更重要的是介绍以下几个知识点:

  1. CSS控制下的动画元素隐藏
  2. 定时器控制下的动画元素隐藏
  3. reverse使用的一般规律

您可以狠狠地点击这里:含提示的图片列表删除demo

本demo含有两个slideup效果,一个是鼠标经过图片时候出现的含有“删除”文字的黑色半透明提示条,如下截图:
slideup效果元素示意 张鑫旭-鑫空间-鑫生活

另外一个就是点击“删除”出现的“是否删除”的提示框,如下截图:
slideup效果元素示意

其中,前面slideup元素的隐藏是通过CSS限制实现的;后者是JavaScript定时器实现的。

在介绍两种元素隐藏方法之前有必要先要脱下slideup动画的衣服,好好窥视其真实的肌体。

slideup+in效果是向上移动到当前位置,距离为自身高度。举个例子,一个身高170cm的妹子站在在二楼吹头发;则该妹子应用slideup+in动画的效果就是:妹子瞬间脑袋顶着1楼天花板,倏地向上移动到正好站在2楼的位置。如果我们在2楼的话,看到的就是妹子的脑袋开始从楼板上冒出来——一直到整个身体出现

妹子slideup效果示意 张鑫旭-鑫空间-鑫生活

1. CSS限制下的slideup效果
这类效果,需要容器(类似上面的楼层)限制(overflow:hidden), HTML结构如下:

外部限制容器(overflow:hidden)
    slideup效果元素(slideup + in/out)

于是,slideup动画执行时候我们就会看到元素慢慢“冒出来”的效果了。

如果没有外部容器的限制,slideup效果就是完整元素(妹子不会被楼层截掉)的上移或下降。这显然不是我们需要的,当out触发的时候,我们希望元素不可见(下降只是位置改变,元素依然可见)。这种情况,就需要借助JS脚本。

2. JavaScript定时器的限制
动画的执行的时候是固定的(CSS限制的),因此,我们可以使用JS让动画效果结束的时候,让元素不可见,如display:none. demo中相关代码如下:

// 点击取消按钮
$("取消按钮").bind("click", function() {
    // 提示框下移动画触发
    $("提示框").addClass("reverse out").removeClass("in");    
    setTimeout(function() {
        // 200毫秒后提示框隐藏
        $("提示框").hide();    
    }, 200);
});

我们无需担心IE6~9之类浏览器的兼容性问题,因为,当元素添加类名out的时候,元素就已经隐藏了,所以延时什么的无需担心影响交互效果。

3. reverse的一般使用规律
如果您希望元素的out动画与in动画是“原路返回”的关系,则需要用到类名”reverse“. 例如demo页面提示框的显示与隐藏完全相反效果,则需要用到reverse.

其使用是一个路子(其他各种animate效果也是如此),我是这样操作的:
元素进入动画:

$("元素").addClass("in").removeClass("reverse out");

元素移出动画:

$("元素").addClass("reverse out").removeClass("in");

addClassremoveClass顺序不分先后。

于是,完整流程的效果即可实现。

初始化的时候,我都是把out, reverse预先放在元素上了,例如这里的:slideup reverse out.

//zxx: 下面为广告~~注意不要勿点~~嘻嘻~~

八、pop效果和flow效果综合实例

pop效果是元素从正面弹出弹入;flow效果是元素先变小然后再向两侧偏移。

您可以狠狠地点击这里:pop/flow效果下的图片移入回车站demo

demo页面的pop-flow交互效果示意 张鑫旭-鑫空间-鑫生活

弹框提示为pop效果,图片移入移出回车站为flow效果。均使用到了reverse使动画效果镜像,flow效果使用了setTimeout定时器控制元素的隐藏。都是上面slideup提到的东西,不再赘述。

不过,回收站的摇动效果可能大家会比较感兴趣,该效果并不出自jQuery Mobile中的animate动画效果之列,而是来自文章一开始提到的那个“蛋一灯”的animate.css中的tada效果。

相关CSS代码demo页面有展示,该动画触发模式与jQuery Mobile更重用的in/out模式不同,其直接添加动画关键字类名就可以了,例如这里,直接:

$().addClass("tada");

就可以了。

九、3D效果之flip翻转

flip效果为中轴翻转,具有代表性的效果就是翻纸牌。

您可以狠狠地点击这里:flip动画与翻转纸牌动画效果

纸牌翻面效果截图 张鑫旭-鑫空间-鑫生活

因为是3D效果,如果希望呈现一定的3D视角,需要在父级元素上添加类名viewport-flip或者直接添加如下CSS:

-webkit-perspective: 1000px;
-moz-perspective: 1000px;

perspective属性具体含义可参见我之前的“CSS3 3D transform详解”一文。

原理简述

  1. 当前在前显示的元素翻转90度隐藏, 动画时间225毫秒
  2. 225毫秒结束后,之前显示在后面的元素逆向90度翻转显示在前
  3. 完成翻面效果

也就是纸牌的前后面在两个不同的时间点进行flip效果,构成完整的纸牌翻面效果。

注:Chrome浏览器下需要让元素屏幕垂直居中,以保证元素均在视角内,避免部分区域不显示的情况发生。

十、3D效果之trun翻转

trun效果为沿着侧边翻转,类似翻书,开关门效果。

您可以狠狠地点击这里:trun动画与门的开关模拟效果demo

turn动画下的门开关效果截图 张鑫旭-鑫空间-鑫生活

与上面flip效果类似,父标签需要添加视角样式,或类名viewport-turn或自己写两行perspective相关CSS.

如果我们把page页面整个应用turn效果,web页面的浏览就像翻书那样,很酷的!

十一、其他相关的总结

绝对定位元素
所有这些animation动画效果,元素本身所占据的空间至始至终都是不变的。因此,类似幻灯片之类多元素切换的效果,势必需要将元素设为绝对定位元素,以占据同一垂直空间。

再考虑到动画会造成强烈的重绘与渲染,从性能角度讲,我们必须将动画元素脱离文档流,也就是设置成绝对定位元素(避免强烈的回流)。

因此,这里,我认为:如果您想让一个元素应用animation驱动的动画效果,请将其设置为绝对定位元素。

与transition动画对比
transition也是有动画效果的,其特定是简单灵活,代码精简。不足之处在于:
1. 不同通过CSS控制动画的起点;
2. 不能设置动画的断点;
3. 动画的驱动与值类型甚至单位有关;
4. 动画只能是一次性的;
5. 动画不能延迟;

等。

各有裨益,这里不展开。

原创文章,转载请注明来自张鑫旭-鑫空间-鑫生活[http://www.zhangxinxu.com]
本文地址:http://www.zhangxinxu.com/wordpress/?p=2748

[转载]JAVA号码工具类:实现手机固话号码判断与区号截取 - - 博客园

mikel阅读(1612)

[转载][原创]JAVA号码工具类:实现手机固话号码判断与区号截取 – – 博客园.

工具类说明

该工具类主要是用于判断号码的类型,如果是手机类型,则返回号码前7位,便于后面继续判断号码归属地;如果是固话类型,则截取固话的区号,同样也是为了后面判断号码的归属地。
在获取到这些信息之后,在结合号码归属地数据库,就能实现完整的归属地功能了。

注:通过淘宝购买手机归属地数据库也算是一个不错的注意,推荐下!

特点

1、使用正则表达式判断手机、固话,基本满足现有手机固话的规则
2、返回的判断结果封装到一个结果类中,结果类中包括号码类型、号码前缀或区号、号码

正则表达式说明

下面是这次主要用到的这则表达式:

//用于匹配手机号码
private final static String REGEX_MOBILEPHONE = "^0?1[3458]\\d{9}$";

//用于匹配固定电话号码
private final static String REGEX_FIXEDPHONE = "^(010|02\\d|0[3-9]\\d{2})?\\d{6,8}$";

//用于获取固定电话中的区号
private final static String REGEX_ZIPCODE = "^(010|02\\d|0[3-9]\\d{2})\\d{6,8}$";

手机号码规则:匹配的号码是第一个为1,第二位为3458,长度为11的号码,前面可以有0.该规则基本满足现有手机的号段。
固定电话规则:根据现有的区号规则总结归纳出来的,区号规则是010只有一个、02开头的三位数、03到09开头的四位数。同样,区号也是可选的,真实的号码长度在6到8位。

具体代码

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class NumberUtil {
    //用于匹配手机号码
    private final static String REGEX_MOBILEPHONE = "^0?1[3458]\\d{9}$";

    //用于匹配固定电话号码
    private final static String REGEX_FIXEDPHONE = "^(010|02\\d|0[3-9]\\d{2})?\\d{6,8}$";

    //用于获取固定电话中的区号
    private final static String REGEX_ZIPCODE = "^(010|02\\d|0[3-9]\\d{2})\\d{6,8}$";

    private static Pattern PATTERN_MOBILEPHONE;
    private static Pattern PATTERN_FIXEDPHONE;
    private static Pattern PATTERN_ZIPCODE;


    static {
        PATTERN_FIXEDPHONE = Pattern.compile(REGEX_FIXEDPHONE);
        PATTERN_MOBILEPHONE = Pattern.compile(REGEX_MOBILEPHONE);
        PATTERN_ZIPCODE = Pattern.compile(REGEX_ZIPCODE);
    }

    public static enum PhoneType {
        /**
         * 手机
         */
        CELLPHONE,

        /**
         * 固定电话
         */
        FIXEDPHONE,

        /**
         * 非法格式号码
         */
        INVALIDPHONE
    }

    public static class Number {
        private PhoneType type;
        /**
         * 如果是手机号码,则该字段存储的是手机号码 前七位;如果是固定电话,则该字段存储的是区号
         */
        private String code;
        private String number;

        public Number(PhoneType _type, String _code, String _number) {
            this.type = _type;
            this.code = _code;
            this.number = _number;
        }

        public PhoneType getType() {
            return type;
        }

        public String getCode() {
            return code;
        }

        public String getNumber() {
            return number;
        }

        public String toString(){
            return String.format("[number:%s, type:%s, code:%s]", number, type.name(), code);
        }
    }

    /**
     * 判断是否为手机号码
     * 
     * @param number
     *            手机号码
     * @return
     */
    public static boolean isCellPhone(String number) {
        Matcher match = PATTERN_MOBILEPHONE.matcher(number);
        return match.matches();
    }

    /**
     * 判断是否为固定电话号码
     * 
     * @param number
     *            固定电话号码
     * @return
     */
    public static boolean isFixedPhone(String number) {
        Matcher match = PATTERN_FIXEDPHONE.matcher(number);
        return match.matches();
    }


    /**
     * 获取固定号码号码中的区号
     * 
     * @param strNumber
     * @return
     */
    public static String getZipFromHomephone(String strNumber) {
        Matcher matcher = PATTERN_ZIPCODE.matcher(strNumber);
        if (matcher.find()) {
            return matcher.group(1);
        }

        return null;
    }

    /**
     * 检查号码类型,并获取号码前缀,手机获取前7位,固话获取区号
     * @param number
     * @return
     */
    public static Number checkNumber(String _number){
        String number = _number;
        Number rtNum = null;

        if(number!=null && number.length()>0){
            if(isCellPhone(number)){
                //如果手机号码以0开始,则去掉0
                if(number.charAt(0) == '0'){
                    number = number.substring(1);
                }

                rtNum = new Number(PhoneType.CELLPHONE, number.substring(0, 7), _number);
            }else if(isFixedPhone(number)){
                //获取区号
                String zipCode = getZipFromHomephone(number);
                rtNum = new Number(PhoneType.FIXEDPHONE, zipCode, _number);
            }else{
                rtNum = new Number(PhoneType.INVALIDPHONE, null, _number);
            }
        }

        return rtNum;
    }

    public static void main(String[] args){
        Number num = checkNumber("013951699549");
        System.out.println(num);

        num = checkNumber("13951699549");
        System.out.println(num);

        num = checkNumber("051687189099");
        System.out.println(num);

        num = checkNumber("02552160433");
        System.out.println(num);

        num = checkNumber("52160433");
        System.out.println(num);
    }
}

返回结果

上面的代码运行后,返回结果为:
1
2
3
4
5

[number:013951699549, type:CELLPHONE, code:1395169]
[number:13951699549, type:CELLPHONE, code:1395169]
[number:051687189099, type:FIXEDPHONE, code:0516]
[number:02552160433, type:FIXEDPHONE, code:025]
[number:52160433, type:FIXEDPHONE, code:null]

需要注意的是,如果号码不合法,则返回的type类型是INVALIDPHONE(非法号码)。
希望上面的类能给您提供帮助,谢谢!

[转载]关于XML与类型Class的映射 - 凤城居士 - 博客园

mikel阅读(937)

[转载]关于XML与类型Class的映射 – 凤城居士 – 博客园.

我们知道数据的持久化是编程必须面对的问题。我们可以保存数据到数据库、Excel表、XML文件、TXT文件等等。那么我们编程中经常会遇到对Xml文件的操作。在http://www.cnblogs.com/fengchengjushi/p/3266191.html这 里我已经简单总结了传统的Xml文件操作办法。也引出了对Xml文件和类型Class之间可以进行映射,从而简化操作,提高效率。那么,很多时候我们并不 一定能自己定义好相应的映射Class。比方说,对业已存在的Xml文件的操作。那么,我们就需要看Xml文件的节点构成,然后反向又Xml文件映射出需 要的Class。接下来,我就以demo为例。

XmlHelper帮助类

复制代码
 public class XmlHelper
    {
        #region XML序列化
        /// <summary>
        /// 文件化XML序列化
        /// </summary>
        /// <param name="obj">对象</param>
        /// <param name="filename">文件路径</param>
        public static void Save(object obj, string filename)
        {
            FileStream fs = null;
            try
            {
                fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
                XmlSerializer serializer = new XmlSerializer(obj.GetType());
                serializer.Serialize(fs, obj);
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (fs != null) fs.Close();
            }
        }

        /// <summary>
        /// 文件化XML反序列化
        /// </summary>
        /// <param name="type">对象类型</param>
        /// <param name="filename">文件路径</param>
        public static object Load(Type type, string filename)
        {
            FileStream fs = null;
            try
            {
                fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                XmlSerializer serializer = new XmlSerializer(type);
                return serializer.Deserialize(fs);
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (fs != null) fs.Close();
            }
        }
        #endregion
    }
复制代码

 

Test.xml文件

复制代码
<?xml version="1.0" encoding="utf-8" ?>
<RuleSettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Gateway Code="AAA">
    <Rules>
      <Rule Code="202" OrderID="1" IsMustExecute="true">
        <Settings>
          <Setting Key="ClassDays_30d" Value="1000" />
        </Settings>
      </Rule>
      <Rule Code="301" OrderID="2" IsMustExecute="true">
        <Settings>
          <Setting Key="GameRole" Value="true" />
        </Settings>
      </Rule>
    </Rules>
  </Gateway>
  <!--PayPalCreditCard-->
  <Gateway Code="BBB">
    <Rules>
      <Rule Code="100" OrderID="1" IsMustExecute="true">
      </Rule>
      <Rule Code="201" OrderID="2" IsMustExecute="true">
        <Settings>
          <Setting Key="SumOrderAmount_7d" Value="500" />
        </Settings>
      </Rule>
      <Rule Code="800" OrderID="23" IsMustExecute="true">
        <Settings>
          <Setting Key="High1" Value="-60" />
          <Setting Key="High2" Value="-30" />
          <Setting Key="High3" Value="-10" />
        </Settings>
      </Rule>
    </Rules>
  </Gateway>
</RuleSettings>
复制代码

 

我们来看这个文件,它的整体结构如下:根节点RuleSettings,它包含Gateway节点的集合。Gateway节点又包含Rules(单 个)节点,Rules节点又包含Rule节点集合,Rule节点下面包含单个Settings(为了讲讲它的特别之处,下面我们对它采取异于Rules节 点的处理方法)节点,这之下包含多个Setting节点。而且,Settings节点可以没有。

现在开始映射对应的Class类型。一个节点实际上是一个XmlElement 也对应一个Class,属性即XmlAttribute就对Class的属性,如果该节点有InnerText就在定义的属性上加XmlText。那么我们对应出如下类型

复制代码
  public class RuleSettings
    {
        public List<Gateway> Gateway { get; set; }
    }

    public class Gateway
    {
        [XmlAttribute("Code")]
        public string Code { get; set; }
        public List<Rule> Rules { get; set; }
    }
    public class Rule
    {
        [XmlAttribute("Code")]
        public string Code { get; set; }
        [XmlAttribute("OrderID")]
        public string OrderID { get; set; }
        [XmlAttribute("IsMustExecute")]
        public bool IsMustExecute { get; set; }
        //[XmlAttribute("Settings")]
        [XmlElement("Settings")]
        public Settings Settings { get; set; }

    }
    /// <summary>
    /// 注:对于这种无XmlAttribute的节点。且它包含子节点。我们这样做时
    /// 必须给Settinghe上面的Settings标明是XmlElment
    /// 不然会多出一个Setting节点
    /// 或者直接在上面使用public List<Setting> Settings { get; set; }
    /// 省去Settings类
    /// </summary>
    public class Settings
    {
        [XmlElement("Setting")]
        public List<Setting> Setting { get; set; }
    }
    public class Setting
    {
        [XmlAttribute("key")]
        public string key { get; set; }
        [XmlAttribute("value")]
        public string value { get; set; }
    }
复制代码

 

其中Rules和Settings节点就是无任何属性,且包含子节点集合的节点。对于Rules我们不考虑映射一个Class,采取在 Gateway节点下面直接定义Rule集合Rules。同时设置它的节点名称为Settings。对于Settings我们同样定义一个类型。它里面有 一个属性是Setting节点的集合。此时。我们必须在Rule类型下的Settings加上XmlElement属性。

接下来就是反序列化了

View Code

 

生成的RuleSetting.xml文件如下

复制代码
<?xml version="1.0"?>
<RuleSettings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Gateway>
    <Gateway Code="AAA">
      <Rules>
        <Rule Code="201" OrderID="1" IsMustExecute="true">
          <Settings>
            <Setting>
              <Setting key="ClassDays_30d" value="12" />
              <Setting key="GameRole" value="true" />
            </Setting>
          </Settings>
        </Rule>
        <Rule Code="301" OrderID="2" IsMustExecute="true">
          <Settings>
            <Setting>
              <Setting key="GameRole" value="true" />
            </Setting>
          </Settings>
        </Rule>
      </Rules>
    </Gateway>
    <Gateway Code="BBB">
      <Rules>
        <Rule Code="100" OrderID="1" IsMustExecute="true" />
        <Rule Code="201" OrderID="2" IsMustExecute="true">
          <Settings>
            <Setting>
              <Setting key="SumOrderAmount_7d" value="600" />
            </Setting>
          </Settings>
        </Rule>
        <Rule Code="800" OrderID="23" IsMustExecute="true">
          <Settings>
            <Setting>
              <Setting key="High1" value="60" />
              <Setting key="High2" value="10" />
              <Setting key="High3" value="10" />
            </Setting>
          </Settings>
        </Rule>
      </Rules>
    </Gateway>
  </Gateway>
</RuleSettings>
复制代码

 

对于根节点 RuleSettings可以加上

[XmlRoot(Namespace = "http://msdn.microsoft.com/vsdata/xsd/vsdh.xsd")]

其实上面处理一个节点下有多个字节点的集合时我们可以采用数组。当然个人习惯。可以自己试试,效果是一样的。

就写到这儿,此处受http://www.cnblogs.com/fish-li/的文章影响,它介绍很细致,你们可以看下。总感觉看了,就想自己写,理解得更透彻点。

2013-10-14 晚

Android TabHost两种使用方法

mikel阅读(933)

1.第一种方式 继承于TabActivity 这种SDK 4.1以后已经废弃了

package com.halleluja.tab2;

import android.app.TabActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.widget.TabHost;
import android.widget.TabHost.OnTabChangeListener;

public class Tab2DemoActivity extends TabActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        TabHost tabHost = this.getTabHost();
        
        LayoutInflater inflater = LayoutInflater.from(this);
        inflater.inflate(R.layout.main, tabHost.getTabContentView());
        
        tabHost.addTab(tabHost.newTabSpec("a").setIndicator("AA").setContent(R.id.tv1));
        tabHost.addTab(tabHost.newTabSpec("b").setIndicator("BB").setContent(R.id.tv2));
        
        tabHost.setOnTabChangedListener(listener);
    }
   
    OnTabChangeListener listener = new OnTabChangeListener() {

                public void onTabChanged(String tabId) {
                        System.out.println(tabId);
                }
           
    };
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:id="@+id/lay"
    >
<TextView  
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="11111111111"
    android:id="@+id/tv1"
    />
    <TextView  
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="222222222"
    android:id="@+id/tv2"
    />
</LinearLayout>

第二种方法:使用布局文件

package com.halleluja.tab;

import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.widget.TabHost;

public class TabDemoActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        TabHost tabHost = (TabHost)findViewById(R.id.tabhost);
        tabHost.setup();
        
        LayoutInflater inflater_tab1 = LayoutInflater.from(this);   
        inflater_tab1.inflate(R.layout.a, tabHost.getTabContentView());  
        inflater_tab1.inflate(R.layout.b, tabHost.getTabContentView());

        tabHost.addTab(tabHost.newTabSpec("aa").setIndicator("111").setContent(R.id.lay1));
        tabHost.addTab(tabHost.newTabSpec("bb").setIndicator("222").setContent(R.id.lay2));
    }
}

package com.halleluja.tab;

import android.app.TabActivity;
import android.os.Bundle;

public class AActivity extends TabActivity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.a);
    }
}

package com.halleluja.tab;

import android.app.TabActivity;
import android.os.Bundle;

public class BActivity extends TabActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.b);
    }
} 

main.xml

<?xml version="1.0" encoding="utf-8"?>  
<TabHost  xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent">  
   
    <LinearLayout
    android:orientation="vertical"  
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"  
    android:padding="5dp">  
        
        
        <TabWidget
        android:id="@android:id/tabs"  
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"  
        android:layout_weight="0" />  
        
        <FrameLayout
        android:id="@android:id/tabcontent"  
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"  
        android:padding="5dp"
        android:layout_weight="1" />  
        
        
    </LinearLayout>  
</TabHost>  

a.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/lay1"
    >
   
    <TextView  
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="aaaaaaaaaaaaaa"
    />
</LinearLayout>

b.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/lay2"
    >
   
    <TextView  
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="bbbbbbbbbbbbb"
    />
</LinearLayout>

如果想要把标签放在底部只要将main.xml修改成:

<?xml version="1.0" encoding="utf-8"?>  
<TabHost  xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent">  
   
    <LinearLayout
    android:orientation="vertical"  
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"  
    android:padding="5dp">  
        <FrameLayout
        android:id="@android:id/tabcontent"  
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"  
        android:padding="5dp"
        android:layout_weight="1" />  
        
        <TabWidget
        android:id="@android:id/tabs"  
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"  
        android:layout_weight="0" />  
        
    </LinearLayout>  
</TabHost>

[转载]tabhost+listview多选 添加 删除 例子 - Code大蛇丸 - 博客园

mikel阅读(929)

[转载]tabhost+listview多选 添加 删除 例子 – Code大蛇丸 – 博客园.

本程序 涉及到了listview的多选 删除 添加。以及tabhost的使用。代码可能有点混乱, 这也是我结合网上资料做的,将就看吧。效果类似ucweb的历史书签管理。当然很难看啊 效果我感觉就是那个意思,比较简陋。见谅了

 

下看下面效果图

默认显示基本设置

点击管理后 的效果

 

点击添加的效果

 

 

复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
>
<!-- RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
-->
<TabHost android:id="@id/android:tabhost"
    android:layout_height="fill_parent"
    android:layout_width="fill_parent"
    android:layout_above="@+id/gridview"
    >
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        >
          <TabWidget android:id="@id/android:tabs"
              android:layout_height="wrap_content"
              android:layout_width="fill_parent"
              android:gravity="left"

              />
          <FrameLayout android:id="@id/android:tabcontent"
              android:layout_height="fill_parent"
              android:layout_width="fill_parent"

              > 
              <LinearLayout android:id="@+id/cb"
                android:orientation="vertical"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                >
                <LinearLayout 
                    android:orientation="vertical"
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent"
                    android:layout_weight="1"

                >
                <CheckBox android:id="@+id/checkbox01" 
                        android:layout_width="fill_parent" 
                         android:layout_height="wrap_content" 
                        android:text=" 启用 自定义配置"
                      />
                 <CheckBox android:id="@+id/checkbox02" 
                        android:layout_width="fill_parent" 
                        android:layout_height="wrap_content" 
                        android:text=" 启用默认配置"
                      />
                </LinearLayout>
                <GridView android:id="@+id/gridview"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                 android:background="@drawable/button"
                    />
                    <!--  -->

               </LinearLayout>
            <LinearLayout android:id="@+id/list"    
                  android:layout_width="fill_parent"
                  android:layout_height="fill_parent"
                  android:orientation="vertical"
                  >
                  <ListView android:id="@+id/listview"
                      android:layout_width="fill_parent"
                      android:layout_height="fill_parent"
                      android:cacheColorHint="#00000000"
                      android:layout_weight="1" 
                      >
                      <!--android:background="@drawable/listviewbackground"  -->
                  </ListView> 
                  <GridView android:id="@+id/gridview01"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:background="@drawable/button"
                />  
              </LinearLayout>
          </FrameLayout>
    </LinearLayout>
</TabHost>
</LinearLayout>
复制代码

 

复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >

 <ImageView 
     android:id="@+id/imgview"
     android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentLeft="true"
    android:padding="5dp"
    />

  <TextView 
    android:id="@+id/link"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="18sp"
    android:padding="3dp"
       android:gravity="center_vertical"
  />

</LinearLayout>
复制代码

 

复制代码
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello World, GreenActivity!</string>
    <string name="app_name">green</string>
    <drawable name="gridviewbackground">#0077AA</drawable>
    <drawable name="listviewbackground">#FFFFFF</drawable> 
    <drawable name="black">#000000</drawable>
    <drawable name="bg">#E6ECF6</drawable>
    <drawable name="fc">#99CCFF</drawable>
    <drawable name="gd">#577FB5</drawable>
</resources>
复制代码

 

 

复制代码
package com.android.green;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map; 
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.app.TabActivity;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color;
import android.opengl.Visibility;
import android.os.Bundle;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.GridView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TabHost;
import android.widget.TabHost.OnTabChangeListener;
import android.widget.TabWidget;
import android.widget.TextView;
import android.widget.Toast;

public class GreenActivity extends TabActivity {
    /** Called when the activity is first created. */
    private GridView configGrid, managerGrid;
    private String configData[] = {"应用","退出"};
    private String managerData[] ={"添加","删除","退出"};
    private ListView listview = null;
    private SimpleAdapter adapter;
    private List list;

    private Map<String, Object> listTemp;
    private EditText et ;
    private CheckBox whiteCb, defWhiteCb;
    private int index = 0;
    private AlertDialog addDlg, exitDlg, removeDlg;
    /*-- configGrid底部菜单选项下标--*/
    private final int CONFIG_ITEM_APPLY = 0;// 应用
    private final int CONFIG_ITEM_EXIT = 1;// 退出
    /*-- managerGrid底部菜单选项下标--*/
    private final int MANAGE_ITEM_ADD = 0; // 添加
    private final int MANAGE_ITEM_REMOVE = 1; // 删除
    private final int MANAGE_ITEM_EXIT = 2; // 退出

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       setContentView(R.layout.main);
        whiteCb = (CheckBox)findViewById(R.id.checkbox01);
        defWhiteCb = (CheckBox)findViewById(R.id.checkbox02);

        whiteCb.setOnCheckedChangeListener(new OnCheckedChangeListener() 
        {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) 
            {
                if (whiteCb.isChecked())
                {
                    System.out.println("white is checked");
                }
            }
        });
        defWhiteCb.setOnCheckedChangeListener(new OnCheckedChangeListener() {

            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

                if(defWhiteCb.isChecked())
                System.out.println("default white is checked");
            }
        });
        // 对话框添加
        et = new EditText(this);
        et.setSingleLine(true);
        addDlg = new AlertDialog.Builder(GreenActivity.this).setTitle("添加页面").setView(et).
        setPositiveButton("确定",new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int which) {

                 addItem(et.getText().toString());
            }

        }).setNegativeButton("取消", null).create() ;

        //退出确认对话框
        exitDlg = new AlertDialog.Builder(this).setTitle("友情提醒").setMessage("确定要退出吗?").
        setPositiveButton("确定",new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int which) {
                // TODO Auto-generated method stub
                finish();
            }
        }).setNegativeButton("取消", null).create();

        // 删除确认对话框
        removeDlg = new AlertDialog.Builder(this).setTitle("删除提示").setMessage("确定删除选中的内容吗?").
        setPositiveButton("确定", new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int which) {
                // TODO Auto-generated method stub
                removeS(list); 
            }
        }).setNegativeButton("取消", null).create();

        //TabHost的相关设置
        int width =45;  
        int height =50;
        final TabHost tabhost = getTabHost();
        tabhost.addTab(tabhost.newTabSpec("checkbox").setIndicator("基本配置").setContent(R.id.cb));
        tabhost.addTab(tabhost.newTabSpec("listview01").setIndicator("管理").setContent(R.id.list));
        final TabWidget tabwidget = tabhost.getTabWidget();

        for (int i = 0; i < tabwidget.getChildCount(); i++)
        {
            //设置页签高度和页签内字体属性
            TextView tv = (TextView)tabwidget.getChildAt(i).findViewById(android.R.id.title);
            tabwidget.getChildAt(i).getLayoutParams().height = height;  
            tabwidget.getChildAt(i).getLayoutParams().width = width; 
       //     tabwidget.setBackgroundResource(R.drawable.toolbar);
            tv.setTextSize(15);
            tv.setTextColor(Color.BLACK);
            tv.setGravity(Gravity.CENTER_HORIZONTAL);
        }
       //配置页gridview的设置
        configGrid = (GridView)findViewById(R.id.gridview);
        configGrid.setNumColumns(configData.length);
        GridAdapter gridAdapter = new GridAdapter(this,Arrays.asList(configData));
        configGrid.setAdapter(gridAdapter);
      //白名单页gridview的设置
        managerGrid = (GridView)findViewById(R.id.gridview01);
        managerGrid.setNumColumns(managerData.length);
        GridAdapter grid01Adapter = new GridAdapter(this,Arrays.asList(managerData));
         managerGrid.setAdapter(grid01Adapter);

         listview = (ListView)findViewById(R.id.listview);
         adapter = new SimpleAdapter(this, getData(),
                  R.layout.listview, new String[]{"link","img"},new int[]{R.id.link, R.id.imgview});                  
        listview.setAdapter(adapter);
        listview.setOnItemClickListener(new OnItemClickListener() {

            // 捕捉点击 对图片进行变换

            public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,long arg3) 
            { 
                Map <String, Object>m1 = (Map <String, Object>)list.get(arg2);
                if (m1.get("img").equals( 2130837509))         //2130837509为图片select.jpg在R中的编号
                {
                    ChangeImg(arg2,true);
                }
                else
                {
                    ChangeImg(arg2,false);
                }
            }

        });

        configGrid.setOnItemClickListener( new OnItemClickListener(){

            @Override
            public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3)
            {
                switch(arg2)
                {
                case CONFIG_ITEM_APPLY:        // 发送白名单 默认白名单的配置 接收配置
                    System.out.println("hello world");
                    break;
                case CONFIG_ITEM_EXIT:  
                    finish();
                    break;
                default:
                }
            }                  
         });

        //白名单管理页面的监听器
         managerGrid.setOnItemClickListener(new OnItemClickListener(){
            @Override
            public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,long arg3) 
            {
                switch(arg2)
                {
                case MANAGE_ITEM_ADD:     // 弹出一个页面用来添加白名单 发送消息
                        addDlg.show();      // 从dialog中接受输入的内容
                    break;
                case MANAGE_ITEM_REMOVE://删除选中的listview项  发送消息
                    removeDlg.show();
                    break;
                case MANAGE_ITEM_EXIT:    // 退出
                    exitDlg.show(); 
                    break;
                default:
                }
            }             
         });

    }

    //变换图像(选中未选中)
    private void ChangeImg(int selectedItem, boolean b)
    {
        SimpleAdapter la = adapter;
        HashMap<String, Object> map = (HashMap<String, Object>)la.getItem(selectedItem);
        if (b)
            map.put("img", R.drawable.radio_on);
        else 
            map.put("img", R.drawable.radio_off);
        la.notifyDataSetChanged();
    }

    public class GridAdapter extends BaseAdapter{
        Context context;
        List list;
        public GridAdapter(Context c,List datalist){
            this.context = c;
            list = datalist;
        }

        public GridAdapter(OnTabChangeListener onTabChangeListener,
                List<String> asList) {
            // TODO Auto-generated constructor stub
        }
        public int getCount() {
            // TODO Auto-generated method stub
            return list.size();
        }

        public Object getItem(int position) {
            // TODO Auto-generated method stub
            return position;
        }
        public long getItemId(int position) {
            // TODO Auto-generated method stub
            return position;
        }

        public View getView(int position, View convertView, ViewGroup arg2) {
            TextView tv;
            if(convertView == null){
                tv = new TextView(context);
            }else{
                tv = (TextView)convertView;
            }
            tv.setLayoutParams( new GridView.LayoutParams(110,48));
            tv.setTextColor(Color.WHITE);
            tv.setGravity(Gravity.CENTER);
            tv.setTextSize(20);
            tv.setText(list.get(position).toString());

            return tv;
        }

    }
    /**
     * 初始化list
     * @return
     */
    private List getData(){
        list = new ArrayList();
        Map<String,Object> map = new HashMap<String,Object>();
        Map<String,Object> map2 = new HashMap<String,Object>();
        Map<String,Object> map3 = new HashMap<String,Object>();
        Map<String,Object> map4 = new HashMap<String,Object>();
        Map<String,Object> map5 = new HashMap<String,Object>();
        Map<String,Object> map6 = new HashMap<String,Object>();
        Map<String,Object> map7 = new HashMap<String,Object>();
        Map<String,Object> map8 = new HashMap<String,Object>();
        Map<String,Object> map9 = new HashMap<String,Object>();

        map.put("img", R.drawable.radio_off);
        map.put("link", "http://3g.uc.cn/portal/index.html");
        map2.put("img", R.drawable.radio_off);
        map2.put("link", "http://mv.ucweb.com/e?id=118");
        map3.put("img", R.drawable.radio_off);
        map3.put("link", " http://mv.ucweb.com/t?id=bookmarks-rss-android");
        map4.put("img", R.drawable.radio_off);
        map4.put("link", "http://mv.ucweb.com/t?id=bookmarks-rss-android");
        map5.put("img", R.drawable.radio_off);
        map5.put("link", "http://mv.ucweb.com/t?id=bookmarks-rss-android");
        map6.put("img", R.drawable.radio_off);
        map6.put("link", "http://mv.ucweb.com/t?id=bookmarks-rss-android");
        map7.put("img", R.drawable.radio_off);
        map7.put("link", "http://mv.ucweb.com/t?id=bookmarks-rss-android");
         map8.put("img", R.drawable.radio_off);
        map8.put("link", "http://mv.ucweb.com/t?id=bookmarks-rss-android");
         map9.put("img", R.drawable.radio_off);
        map9.put("link", "http://mv.ucweb.com/e?id=118"); 

        list.add(map);
        list.add(map2);
        list.add(map3);
        list.add(map4);
        list.add(map5);
        list.add(map6);
        list.add(map7);
        list.add(map8);
        list.add(map9);
        return list;
    }

    //删除listview被选中的item
     public void removeS(List list)
    {
          ArrayList<Integer>  indexTemp = new ArrayList<Integer>();
          int size = list.size();
          int temp;
          System.out.println("list.size = "+size);
          for (int i = 0; i < size; i++)
          {
               listTemp = (Map<String, Object>)list.get(i);
               if (listTemp.get("img").equals(2130837510))
               {
                   indexTemp.add(i);
               }
          }
          if (0 != indexTemp.size())
          {
              for (int i=  0; i<indexTemp.size(); i++)
              {
                  if (i == 0)
                  {
                      temp =  (Integer)indexTemp.get(i);
                  }
                  else
                  {
                      temp = (Integer)indexTemp.get(i)-i;

                  }
                  list.remove(temp);
                  adapter.notifyDataSetChanged(); 
              }
          }      
    }

     //为listview 添加item
     public void addItem(String linkString)
     {
         HashMap<String, Object> map = new HashMap<String, Object>();
         map.put("img", R.drawable.radio_off);
         map.put("link",linkString);
         list.add(map);
         adapter.notifyDataSetChanged();
     }

}
复制代码

 

[转载]Android 4.3 SDK Samples 阅读笔记之一 ContactManager - [武汉]胖大海 - 博客园

mikel阅读(922)

[转载]Android 4.3 SDK Samples 阅读笔记之一 ContactManager – [武汉]胖大海 – 博客园.

一、综述:

ContactManager是一个关于联系人管理的示例代码,共包含二个Activity,ContactManager是启动Activity,用于显示联系人列表及添加按钮;ContactAdder是添加联系人用的Activity。

在默认使用模拟器调试项目时,如果没有添加过联系人帐户,添加功能会报错。原因是Android对于联系人的管理与传统手机 不一样,Android的联系人可以与帐户关联,这个帐户可以是一个电子邮箱地址或者是Exchange的帐户。当然,与可以不与某个帐户关联,这种被称 为本地联系人。

进入Android自带的联系人管理界面,当没有任何联系人时界面如下:

其中登录帐户,就是指创建一个帐户,帐户在Android的系统设置中的帐户管理里也可以创建。在未创建帐户直接创建新联系人时界面如下:

 

在添加界面上方,即显示了“仅保存在手机中,不同步…”信息。

 

ContactManager项目中添加联系人,必须要选择所属的帐户,如果没有选择帐户,则会报错,这个错误,后文中将在改进部分中修复此问题。

二、阅读笔记:

1.ContactManager.java源码分析

该类中包含方法如下:

private void populateContactList():显示联系人列表的方法,在OnCreate方法的最后一行调用。

private Cursor getContacts():取得所有联系人的游标的方法,在populateContactList方法第一行调用。

protected void launchContactAdder():显示添加联系人窗体的方法,在mAddAccountButton按钮的 onClick事件的处理程序中调用。

1.1依照相关顺序,首先解析getContacts的源码,在getContacts方法中,主要使用了managerQuery方法,返回了一个游标。

managerQuery方法接受的参数意义依次为:

Uri uri:查询目标Content Provider的唯一标识,取值ContactsContract.Contacts.CONTENT_URI代表查询联系人。

String[] projection:返回字段的名称列表,SQL查询语句中的SELECT子句以数组方式书写。

String selection:查询的条件,SQL中WHERE子句。

String[] selectionArgs:查询的条件中的参数值列表,对应替换selection中“? ”占位符。

String sortOrder:排序方式,SQL中的ORDER BY子句

返回值Cursor是一种游标对象,用于顺序读取查询结果。

1.2 populateContactList方法中,首先调用了getContacts,获得一个包含所有联系人的游标对象。

随之,构造了一个SimpleCursorAdapter的适配器对象,用于将Cursor数据源中的数据绑定到视图中显示。

SimpleCursorAdapter对象的构造方法参数意义依次为:

Context contextListView相关联的SimpleListItemFactory运行的上下文对象,即ListView所在的Activity对象。

int layout:用于显示Cursor中返回的每一行数据的layout

Cursor c:数据源的游标对象。

String[] from:要显示数据的字段名称列表。

Int[] to:用于显示数据的控件的id列表。

根据源码可以得知,将使用layout下的contact_entry.xml作为游标中每一行数据显示的View,显示的字段只有一个,就是ContactsContract.Data.DISPLAY_NAME,显示数据的控件idcontactEntryText。

查看res文件夹下的layout文件夹下的contact_entry.xml可以发现,contactEntryText是一个TextView控件。

在本示例中,在managerQuery方法中的查询字段使用的是ContactsContract.Contacts.DISPLAY_NAME,而在populateContactList方法中显示字段时使用的是ContactsContract.Data.DISPLAY_NAME,两次使用的常量不同,但通过查询发现,两个常量的值都为“display_name”,可以交换使用,直接使用其值,程序也可正常运行。

1.2 launchContactAdder方法在mAddAccountButton控件的onClick事件的Listener中调用,主要作用是显示添加联系人的界面。

2.ContactAdder.java源码分析

ContactAdder类相对而言要复杂一些,本身实现了OnAccountsUpdateListener接口,这个接 口内包含一个方法onAccountsUpdated。实现了该接口的类,可以由AccountManager注册成为OnAccountsUpdate 事件的监听者,当系统中的帐户发生更新时,由AccountManager调用所有监听者的onAccountsUpdated方法。

ContactAdder类中实现了如下方法:

private static AuthenticatorDescription getAuthenticatorDescription(String type, AuthenticatorDescription[] dictionary): 返回帐户的签发者的相关信息。在onAccountsUpdated方法中调用,用于读取帐户的显示数据。

private void updateAccountSelection():将mAccountSpinner下拉列表 中当前选中的帐户信息赋给内部字段mSelectedAccount,该内部字段指示添加的联系人属于哪个帐户。此方法在mAccountSpinner 控件的OnItemSelected事件的处理方法中调用。

private void onSaveButtonClicked():处理添加按钮的单击,在mContactSaveButton按钮的onClick事件处理程序中调用。

protected void createContactEntry():添加一个新的联系人,在onSaveButtonClicked事件中调用。

另外该类中还包括两个内部类:

AccountData:主要是帐户的显示信息,包括帐户名称,帐户的签发者的图标,签发者的名称。另外还有一个type字段,主要是type签发者的唯一识别标志,取图片和名称需要使用,并不用于显示。

AccountAdapter:用于帐户信息显示的适配器类,继承自ArrayAdapter。

在解析各个方法的代码前,先解析OnCreate方法中的内容。

在OnCreate方法中,构建了两个集合,一个为电话类型的集合,一个为Email类型的集合。在Android系统中, 一个联系人,可以有多个电话,多个Email,这些电话和Email可以标识为办公,家庭等不同类型。使用了ArrayAdapter将 mContactPhoneTypeSpinner控件和mContactEmailTypeSpinner分别与构建的两个集合关联,并使用 getTypeLabel()方法查询对应的显示名称进行显示。

而且在OnCreate方法中,构造了内部类AccountAdapter的实例,追踪构造方法,发现使用 setDropDownViewResource(R.layout.account_entry)方法,设置了下拉列表的View使用的layout, 并使用AccountAdapter将内部字段mAccounts作为mAccountSpinner控件的数据源。mAccounts是一个 AccountData类的集合,也即是帐户信息的集合。

在138行使用了 AccountManager.get(this).addOnAccountsUpdatedListener(this, null, true); 将ContactAdder注册成为OnAccountsUpdate事件的监听者。此时,会立刻调用一次 CContactAdder.onAccountsUpdated()方法。该方法先清除帐户信息集合,再读取所有的帐户,并根据帐户名称和签发者的相关 信息构造帐户显示信息类的集合,赋值给mAccountSpinner控件的数据源内部变量mAccounts,,并调用AccountAdapter类 的notifyDataSetChanged方法,通知Adapter更新mAccountSpinner控件的显示同步数据来源的变化。

最复杂的方法是CreateContactEntry方法,要理解该方法的代码,需要理解Android对通迅录的组织。

每个联系人属于一个帐户,首先是在该帐户下添加一个记录,获得RAW_CONTACT_ID。

然后使用RAW_CONTACT_ID添加联系人信息,添加电话信息,添加Email信息。

在帐户下添加记录时,操作的Uri为ContactsContract.RawContacts.CONTENT_URI, 返回值为RAW_CONTACT_ID,而添加联系人信息,电话信息,和Email信息时操作的是 ContactsContract.Data.CONTENT_URI,使用RAW_CONTACT_ID字段与帐户下的记录关联。

本例中使用了批量操作,故先使用以下代码构建了一个操作集合。

 

随后使用了一系列代码构建了批量操作,并添加到该集合中。构建批量操作中的第一个操作的代码如下:

 

第一个操作中为添加联系人记录,分别添加了帐户的类型和名称两个值,返回该记录的ID值。操作中使用了如下方法:

newInsert(Uri uri)方法表示,插入操作使用的Uri。

withValue(String name,Object value)方法表,要操作的字段名和其值。

build()方法表示,将其构建成为一个操作。

与构建第一个操作类似的代码构建了接下来的三个操作,分别用于添加联系人名称,电话号码,Email地址。以构建添加联系人的操作为例,其代码如下:

 

添加了联系人的名称操作使用了withValueBackReference方法。此方法中第一个参数表示添加记录的字段名,第二个参数表示了使用批量操作中下,标为0的操作的返回值也就是添加联系人记录的操作,其返回值即是联系人的记录Id。

添加电话号码和邮件地址,只是字段不同而已。

最后使用 getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops);执行了批量操 作。getContentResolver方法用于获取ContentResolver对象,该对象是执行操作的执行者,applyBatch方法用于执 行批量的操作,第一个参数是有该操作权限对象的进程名称,第二个参数是要执行的操作集合。

三、改进源码

3.1 错误排除

首先要做的改进是,允许在没有帐户的情况下增加联系人。

在添加联系人的批量操作中的第一个,会使用mSelectedAccount中存储的对象的getType()和 getName方法分别获取选中的帐户的类型和名称,如果系统中没有帐户,mSelectedAccount的值将为null,所以在使用前判定 mSelectedAccount是否为null.

将代码:

 

修改为:

 

在没有帐户被选中的情况下,添加记录时,帐户的类型和名称都使用null值,这样添加的联系人记录就是仅本地存储,不与任何帐户关联的。

在操作中加入AGGREGATION_MODE字段,设其值为AGGREGATION_MODE_DISABLED,主要目的是阻止同名联系人的电话和Email等资料自动合并处理。

另外,还需要注释掉使用到mSelectedAccount的语句。

 

3.2 新版本的建议做法

在本示例代码中,可以发现ManagerQuery方法与SimpleCursorAdapter类的构造方法被标识为过时的方法。

在新版本的SDK的Document中建议使用CursorLoader类来替代ManagerQuery方法。CursorLoader类会创建一个用于游标的Loader类,该类由LoaderManager对象管理。需要实现三个方法:

onCreateLoader(int id,Bundle args):该方法在LoaderManager对象创建Loader时使用。

onLoadFinished(Loader<T> arg0,T arg1):该方法在Loader读取数据完成后调用。

onLoaderReset(Loader<T> arg0):该方法在Loader重置时调用。

重写的populateContactList2方法代码如下:

 

本示例代码中构建了populateContactList2方法,替换了原代码中的populateContactList和getContacts方法。

注意与原来代码的区别,依然是构造了一个SimpleCursorAdapter对象,只是将其中的第三个参数Cursor 设为null值。主要原因是代码中使用CursorLoader去管理Cursor,不是直接在代码中构造Cursort,故在创建 SimpleCursorAdapter对象时无法传入Cursor对象。

接下来使用getLoaderManager取得了当前Activity的LoaderManager对象,调用该对象的initLoader方法,初始化一个Loader对象。

intiLoader方法有三个参数:第一个参数为int id,是一个标识符,用于LoaderManager实例区分不 同的Loader;第二个参数为Bundle args,用于向Loader中传入数据,第三个参数是LoaderCallBack<T>接 口的实现类。由参数类型LoaderCallBack<T>可以看出这是一个回调类,主要用于LoaderManager管理该Loader 对象时三个方法,在上文中已经介绍过。同时该接口为一个泛型接口,可以用于管理不同类型的Loader。本例中用于管理Cursor对象,故传入值为 LoaderCallBack<Cursor>。

在LoaderCallBack<Cursor>的onCreateLoader中创建了一个 CursorLoader类的实例返回,CursorLoader类是Loader<Cursor>的实现。在onLoadFinished 方法中使用如下代码将CursorLoader加载完成后获得的Cursor对象置入SimpleCursorAdapter对象。

 

在onLoaderReset方法中使用如下代码,将未加载完成的Cursor对象从SimpleCursorAdapter对象中卸载。

 

为了配合上述代码,将原先在populateContactList方法中声明的SimpleCursorAdapter对象声明放入类中。由于CursorLoader是在Android 3.0中加入的新类型,故需要将程序的最低SDK需求设为3.0,即将minSdkVersion的值改为11

 最后将onCreate中的两处对populateContactList方法的调用改为populateContactList2方法调用。

至此,ContactManager类的改造完成,使用了新的SDK的实现方式,并修复了无帐户时添加联系人的错误。

改造后的项目源码下载地址:http://files.cnblogs.com/PandaHi/ContactManager.rar

[转载]Android UI开发第三十二篇——Creating a Navigation Drawer - 张兴业 - 博客园

mikel阅读(953)

[转载]Android UI开发第三十二篇——Creating a Navigation Drawer – 张兴业 – 博客园.

 

        Navigation Drawer是从屏幕的左侧滑出,显示应用导航的视图。官方是这样定义的:

 

    The navigation drawer is a panel that displays the app’s main 
    navigation options on the left edge of the screen. It is hidden 
    most of the time, but is revealed when the user swipes a finger 
    from the left edge of the screen or, while at the top level of the 
    app, the user touches the app icon in the action bar.

 

如下图指示:

 

       Navigation Drawer不同于SlidingDrawer,它不存在可以拖动的handle;它也不同于SlidingMenu,Navigation Drawer滑出时主屏幕视图不一定。Navigation Drawer是覆盖在主视图上的。

       Navigation Drawer是Android团对在2013 google IO大会期间更新的Support库(V13)中新加入的重要的功能。实现现Navigation Drawer需要使用最新支持库(V13)的DrawerLayout。Navigation Drawer的设计指南请参考 

Navigation Drawer design guide

Create a Drawer Layout

       创建Navigation Drawer需要DrawerLayout 作为界面根控件。在DrawerLayout里面第一个View为当前界面主内容;第二个和第三个View为Navigation Drawer内容。如果当前界面只需要一个Navigation Drawer,则第三个View可以省略。

 


      下面的例子中DrawerLayout里面包含两个View,第一个FrameLayout中是当前界面主要内容显示区域;第二个ListView为Navigation Drawer内容。

 

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!-- The main content view -->
    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <!-- The navigation drawer -->
    <ListView android:id="@+id/left_drawer"
        android:layout_width="240dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp"
        android:background="#111"/>
</android.support.v4.widget.DrawerLayout>

 

 

上面的代码中有如下几点需要注意:

    • 1、显示界面主要内容的View (上面的 FrameLayout ) 必须为DrawerLayout的第一个子View, 原因在于 XML 布局文件中的View顺序为Android系统中的 z-ordering顺序,而Navigation Drawer必须出现在内容之上。
    • 2、显示界面内容的View宽度和高度设置为和父View一样,原因在于当Navigation Drawer不可见的时候,界面内容代表整个界面UI。
    • 3、Navigation Drawer (上面的 ListView必须使用android:layout_gravity属性设置水平的 gravity值 .如果要支持 right-to-left (RTL,从右向左阅读)语言 用 "start" 代替 "left" (当在 RTL语言运行时候,菜单出现在右侧)。
    • 4、抽屉菜单的宽度为 dp 单位而高度和父View一样。抽屉菜单的宽度应该不超过320dp,这样用户可以在菜单打开的时候看到部分内容界面。

 

Initialize the Drawer List

 

 

       在您的Activity中需要先初始化Navigation Drawer内容,根据您的应用需要Navigation Drawer的内容可能不是ListView,可以使用其他View。

 

      在上面的示例中,我们需要给Navigation Drawer的ListView设置一个Adapter来提供数据。如下所示:

 

public class MainActivity extends Activity {
    private String[] mPlanetTitles;
    private DrawerLayout mDrawerLayout;
    private ListView mDrawerList;
    ...

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

        mPlanetTitles = getResources().getStringArray(R.array.planets_array);
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerList = (ListView) findViewById(R.id.left_drawer);

        // Set the adapter for the list view
        mDrawerList.setAdapter(new ArrayAdapter<String>(this,
                R.layout.drawer_list_item, mPlanetTitles));
        // Set the list's click listener
        mDrawerList.setOnItemClickListener(new DrawerItemClickListener());

        ...
    }
}

      上面的代码调用了 setOnItemClickListener() 函数来接受Navigation Drawer点击事件。下面会介绍如何通过点击Navigation Drawer显示主界面内容。

Handle Navigation Click Events

 

       当用户选择Navigation Drawer List中的条目时,系统会调用  OnItemClickListener的 onItemClick()函数

 

       根据您的应用需要,onItemClick函数的实现方式可能不同。下面的示例中,选择Navigation Drawer条目会在程序主界面中插入不同的 Fragment 。

private class DrawerItemClickListener implements ListView.OnItemClickListener {
    @Override
    public void onItemClick(AdapterView parent, View view, int position, long id) {
        selectItem(position);
    }
}

/** Swaps fragments in the main content view */
private void selectItem(int position) {
    // Create a new fragment and specify the planet to show based on position
    Fragment fragment = new PlanetFragment();
    Bundle args = new Bundle();
    args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);
    fragment.setArguments(args);

    // Insert the fragment by replacing any existing fragment
    FragmentManager fragmentManager = getFragmentManager();
    fragmentManager.beginTransaction()
                   .replace(R.id.content_frame, fragment)
                   .commit();

    // Highlight the selected item, update the title, and close the drawer
    mDrawerList.setItemChecked(position, true);
    setTitle(mPlanetTitles[position]);
    mDrawerLayout.closeDrawer(mDrawerList);
}

@Override
public void setTitle(CharSequence title) {
    mTitle = title;
    getActionBar().setTitle(mTitle);
}

 

 

Listen for Open and Close Events

 

 

        如果需要监听菜单打开关闭事件,则需要调用 DrawerLayout类的 setDrawerListener() 函数,参数为 DrawerLayout.DrawerListener接口的实现该接口提供了菜单打开关闭等事件的回调函数,例如 onDrawerOpened() 和onDrawerClosed().

 

    如果您的Activity使用了 action bar,则您可以使用Support库提供的 ActionBarDrawerToggle 类,该类实现了 DrawerLayout.DrawerListener接口,并且您还可以根据需要重写相关的函数。该类实现了菜单和Action bar相关的操作。

 

    根据在 Navigation Drawer 设计指南中的介绍,当菜单显示的时候您应该根据情况隐藏ActionBar上的功能菜单并且修改ActionBar的标题。下面的代码演示了如何重写 ActionBarDrawerToggle 类的相关函数来实现该功能。

 

public class MainActivity extends Activity {
    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;
    private CharSequence mDrawerTitle;
    private CharSequence mTitle;
    ...

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

        mTitle = mDrawerTitle = getTitle();
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
                R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) {

            /** Called when a drawer has settled in a completely closed state. */
            public void onDrawerClosed(View view) {
                getActionBar().setTitle(mTitle);
                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
            }

            /** Called when a drawer has settled in a completely open state. */
            public void onDrawerOpened(View drawerView) {
                getActionBar().setTitle(mDrawerTitle);
                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
            }
        };

        // Set the drawer toggle as the DrawerListener
        mDrawerLayout.setDrawerListener(mDrawerToggle);
    }

    /* Called whenever we call invalidateOptionsMenu() */
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        // If the nav drawer is open, hide action items related to the content view
        boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
        menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);
        return super.onPrepareOptionsMenu(menu);
    }
}

 

Open and Close with the App Icon

 

 

    用户可以从屏幕边缘滑动来打开Navigation Drawer,如果您使用了 action bar,应该让用户通过点击应用图标也可以打开抽屉菜单。并且应用图标也应该使用一个特殊的图标来指示抽屉菜单。您可以使用 ActionBarDrawerToggle 类来实现这些功能。

 

    使用 ActionBarDrawerToggle ,先通过其构造函数来创建该对象,构造函数需要如下参数:

    • 1)显示Navigation Drawer的 Activity 对象
    • 2) DrawerLayout 对象
    • 3)一个用来指示Navigation Drawer的 drawable资源
    • 4)一个用来描述打开Navigation Drawer的文本 (用于支持可访问性)。
    • 5)一个用来描述关闭Navigation Drawer的文本(用于支持可访问性).
  •  

    无论你是否继承 ActionBarDrawerToggle 来实现Navigation Drawer监听器,您都需要在Activity的生命周期函数中调用ActionBarDrawerToggle 的一些函数。

如下所示:

 

public class MainActivity extends Activity {
    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;
    ...

    public void onCreate(Bundle savedInstanceState) {
        ...

        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerToggle = new ActionBarDrawerToggle(
                this,                  /* host Activity */
                mDrawerLayout,         /* DrawerLayout object */
                R.drawable.ic_drawer,  /* nav drawer icon to replace 'Up' caret */
                R.string.drawer_open,  /* "open drawer" description */
                R.string.drawer_close  /* "close drawer" description */
                ) {

            /** Called when a drawer has settled in a completely closed state. */
            public void onDrawerClosed(View view) {
                getActionBar().setTitle(mTitle);
            }

            /** Called when a drawer has settled in a completely open state. */
            public void onDrawerOpened(View drawerView) {
                getActionBar().setTitle(mDrawerTitle);
            }
        };

        // Set the drawer toggle as the DrawerListener
        mDrawerLayout.setDrawerListener(mDrawerToggle);

        getActionBar().setDisplayHomeAsUpEnabled(true);
        getActionBar().setHomeButtonEnabled(true);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        // Sync the toggle state after onRestoreInstanceState has occurred.
        mDrawerToggle.syncState();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDrawerToggle.onConfigurationChanged(newConfig);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Pass the event to ActionBarDrawerToggle, if it returns
        // true, then it has handled the app icon touch event
        if (mDrawerToggle.onOptionsItemSelected(item)) {
          return true;
        }
        // Handle your other action bar items...

        return super.onOptionsItemSelected(item);
    }

    ...
}

 

/**
* @author 张兴业
*  iOS入门群:83702688
*  android开发进阶群:241395671
*  我的新浪微博:@张兴业TBOW
*/

[转载]用Tabhost以及Fragment实现标签式主界面框架 (Android学习笔记) - Kecp - 博客园

mikel阅读(1019)

[转载]用Tabhost以及Fragment实现标签式主界面框架 (Android学习笔记) – Kecp – 博客园.

      吐槽开始学习Android,第一个掉入的坑就是标签式界面。好像Android一开始的版本就不是很好支持这个东西。

后来才出了Fragment这个新的UI来将一个页面作为一个UI控件来使用的功能。觉得Android开发比iPhone开发麻烦,虽然我

没搞过iPhone。。。。

摘要  本文将介绍如何使用Tabhost以及Fragment控件来实现一个基本的标签界面,接着会介绍如果实现返回等操作界面

堆栈的功能。

前戏 准备环境配置等工作–  要注意的是,在Android3.0以上才加入了Fragment控件,为了兼容低版本的系统,我们需要用Android

sdk manager 下载并引用Android的支持库文件support library,移步官网这里有详细安装介绍。

正文

以下是主界面 main_tabhost_activity.xml文件,其中tab1、tab2、tab3是我们要进行切换的界面,TabWidget是标签的内容,随后我们通过

代码添加标签按钮。

复制代码
<?xml version="1.0" encoding="utf-8"?>
<TabHost android:id="@+id/tabhost"   
  android:layout_width="fill_parent"   
  android:layout_height="fill_parent"   
  xmlns:android="http://schemas.android.com/apk/res/android">  
    <LinearLayout android:layout_width="fill_parent"   
      android:id="@+id/linearLayout1"   
      android:layout_height="fill_parent"   
      android:orientation="vertical">
        <FrameLayout 
             android:layout_weight="1"
             android:layout_width="fill_parent" 
             android:layout_height="wrap_content" 
             android:id="@android:id/tabcontent">  
            <FrameLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/tab1"></FrameLayout>  
            <FrameLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/tab2"></FrameLayout>  
            <FrameLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:id="@+id/tab3"></FrameLayout>  
        </FrameLayout>
        <TabWidget android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:layout_weight="0"
          android:tag="tabs"
          android:id="@android:id/tabs"></TabWidget> 
    </LinearLayout>
</TabHost>
复制代码

对应的Activity类要继承 FragmentActivity,并执行 OnTabChangeListener 接口

复制代码
public class MainTabHost extends FragmentActivity implements OnTabChangeListener{
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mail_tabhost_activity);
        setupTabs();
    }
    ...
}
复制代码

 

接着我们开始初始化TabHost控件,并在底部加入三个自定义的标签按钮

复制代码
// 初始化标签按钮
    private void setupTabs() {
        mTabHost = (TabHost) this.findViewById(R.id.tabhost);
        mTabHost.setup();
        // 生成底部自定义样式的按钮
        String[] title = new String[] { "主页", "帮助", "Kecp" };
        int[] tabIds = new int[] { R.id.tab1, R.id.tab2, R.id.tab3 };
        for (int i = 0; i < title.length; i++) {
            Button button = new Button(this);
            button.setText(title[i]);
            button.setBackgroundDrawable(this.getResources().getDrawable(
                    R.drawable.tab_lable));  //自定义按钮样式
            mTabHost.addTab(mTabHost.newTabSpec(title[i]).setIndicator(button)
                    .setContent(tabIds[i]));
        }

        mTabHost.setOnTabChangedListener(this);
    }
复制代码

其中按钮的背景图代码如下,在drawable文件夹下新建一个tab_lable.xml文件

复制代码
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="true">
        <color android:color="#ffff0000"/>
    </item>
    <item android:state_selected="false">
        <color android:color="#ff00ff00"/>
    </item>
    <item>
        <color android:color="#ff00ff00"/>
    </item>

</selector>
复制代码

这时候运行就可以看到以下效果图:

但现在还没有切换功能哦,因为我们执行了setOnTabChangedListener代码,每点击底部的按钮都会触发onTabChanged函数,

所以我们可以在onTabChanged函数中实现点击切换界面的功能。又贴代码:

复制代码
@Override
    public void onTabChanged(String tag) {
        Fragment frag = null;
        int contentViewID = 0;
        if (tag.equals("主页")) {
            frag = new FirstPageFragment(); //自定义继承Fragment的UI,放了一个简单的显示文本标题的控件。
            contentViewID = R.id.tab1;
        } else if (tag.equals("帮助")) {
            frag = new HelpFragment();
            contentViewID = R.id.tab2;
        }
        if (frag == null)
            return;

        FragmentManager manager = this.getSupportFragmentManager();

        if (manager.findFragmentByTag(tag) == null) {
            FragmentTransaction trans = manager.beginTransaction();
            trans.replace(contentViewID, frag, tag);
            trans.commit();
        }

    }
复制代码

这样点击下边按钮时候便可以切换了,而且对应标签页的按钮也会显示未红色。其中FirstPageFragment只是一个简单的类,继承了Fragment。

实际开发中这个界面就作为我们某一个功能的主界面来使用了。相当于一个Activity。虽然实现很简单,但还是把代码都贴出来方便各位吧~~

复制代码
public class FirstPageFragment extends Fragment implements OnClickListener{
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                    Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.first_page_fragment, null);
        Button btn = (Button)view.findViewById(R.id.button1);
        btn.setOnClickListener(this);
        return view;
    }
}
复制代码

 

复制代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:text="首页" >

    </TextView>

</LinearLayout>
复制代码

下次为大家分享TabHost的界面堆栈问题。