Android模拟器 Genymotion 安装使用【图解教程】

 更新时间:2016年9月20日 19:57  点击:2000
Genymotion是一套完整的工具,它提供了Android虚拟环境。它简直就是开发者、测试人员、推销者甚至是游戏玩家的福音。而且Genymotion支持Windows、Linux和Mac OS等操作系统,容易安装和使用,本文就来看看 Genymotion 安装使用图解教程.

Genymotion模拟器提供了Android虚拟环境,为安卓开发测试人员带来了极大的便利。Genymotion的启动速度比Eclipse自带的模拟器要快得多,运行起来非常的流畅,堪称Eclipse拥趸的调试神器!

Genymotion兼容的操作系统有:Microsoft Windows 32/64 bits、Mac OSX 10.5+ 、 Linux 32/64 bits

Genymotion官网:https://www.genymotion.com 


Android Studio本身的模拟器已经很强大,所以Genymotion还是配合Eclipse使用比较多一点,下面也是基于Eclipse编辑器进行讲解。

1、翻墙

Genymotion的使用需要注册账户并登录,但如果没有翻墙的话,注册和登录都是很难操作成功的。

在这里介绍个VPN,可以免费使用一段时间,足够安装完环境了。

Green VPN官网:https://www.igreenjsq.co

在官网注册一个账户,打开注册时填写的邮箱激活一下账户。下载PC客户端,登录账号,直接点击“链接”按钮:


VPN连接成功了,但对于一个程序员来说,上个技术类的网站都要翻墙,简直是一种耻辱!

2、注册账号并下载安装Genymotion

Genymotion基于VirtualBox虚拟机才能运行,在下载Genymotion时应选择相应的版本(软件下载不需要翻墙):


“Get Genymotion (without VirtualBox) (24.03MB)”版本适合于已经安装了VirtualBox的电脑,“Get Genymotion (126.02MB)”版本适合于尚未安装VirtualBox的电脑,安装该版本完成的时候会提示是否安装VirtualBox环境,点击“是”按钮,就顺带的把VirtualBox也安装了:


3、添加虚拟机

运行Genymotion,软件会提示是否添加一个虚拟设备:


点击“Yes”,弹出登录界面:


动不动就要登录,这也是我非常想吐槽的一个地方!

如果安装了翻墙软件,登录是没问题的;没有安装翻墙软件的话,也可以在网上找一个代理IP,设置一下:


登录成功后会出现一个设备列表:


选择需要用到的虚拟设备进行安装,120MB左右一个,下载过程可以不开VPN。

4、Eclipse安装Genymotion插件


Name:Genymobile Location:http://plugins.genymotion.com/eclipse/ ,点击“OK”


选择并下载(记得开VPN):


安装完成,重新启动Eclipse,可以看到重启后软件界面多了一个小图标:


点击图标,提示需要配置Genymotion安装的目录:


点击“OK”,进入配置界面:


点击“OK”进入虚拟设备选择界面,选择相应的虚拟设备,并启动:


Genymotion启动成功:


运行一个测试程序:


程序已经成功的在Genymotion模拟器中运行了:


PS:关闭Genymotion运行程序的话,默认启动的是Eclipse自带的模拟器。

现在,我们的Genymotion模拟器安装就告一段落了。


PopupWindow在android.widget包下,弹出窗口的形式展示。PopupWindow弹出的菜单随内容的宽度自适应,重写ListView的onMeasure()方法是个不错的解决办法。

PopupWindow用法

使用PopupWindow可实现弹出窗口效果,,其实和AlertDialog一样,也是一种对话框,两者也经常混用,但是也各有特点。下面就看看使用方法。
首先初始化一个PopupWindow,指定窗口大小参数。

PopupWindow mPop = new PopupWindow(getLayoutInflater().inflate(R.layout.window, null),
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
也可以分开写:
LayoutInflater mLayoutInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
//自定义布局
ViewGroup menuView = (ViewGroup) mLayoutInflater.inflate(
                    R.layout.window, null, true);
PopupWindow mPop = new PopupWindow(menuView, LayoutParams.WRAP_CONTENT,
                    LayoutParams.WRAP_CONTENT, true);
当然也可以手动设置PopupWindow大小。
mPop.setContentView(menuView );//设置包含视图
mPop.setWidth(int )
mPop.setHeight(int )//设置弹出框大小

设置进场动画:
mPop.setAnimationStyle(R.style.AnimationPreview);//设置动画样式


mPop.setOutsideTouchable(true);//这里设置显示PopuWindow之后在外面点击是否有效。如果为false的话,那么点击PopuWindow外面并不会关闭PopuWindow。当然这里很明显只能在Touchable下才能使用。

当有mPop.setFocusable(false);的时候,说明PopuWindow不能获得焦点,即使设置设置了背景不为空也不能点击外面消失,只能由dismiss()消失,但是外面的View的事件还是可以触发,back键也可以顺利dismiss掉。当设置为popuWindow.setFocusable(true);的时候,加上下面两行设置背景代码,点击外面和Back键才会消失。
mPop.setFocusable(true);
需要顺利让PopUpWindow dimiss(即点击PopuWindow之外的地方此或者back键PopuWindow会消失);PopUpWindow的背景不能为空。必须在popuWindow.showAsDropDown(v);或者其它的显示PopuWindow方法之前设置它的背景不为空:

mPop.setBackgroundDrawable(new ColorDrawable(0));



mPop.showAsDropDown(anchor, 0, 0);//设置显示PopupWindow的位置位于View的左下方,x,y表示坐标偏移量

mPop.showAtLocation(findViewById(R.id.parent), Gravity.LEFT, 0, -90);(以某个View为参考),表示弹出窗口以parent组件为参考,位于左侧,偏移-90。
mPop.setOnDismissListenerd(new PopupWindow.OnDismissListener(){})//设置窗口消失事件

注:window.xml为布局文件

总结:

1、为PopupWindow的view布局,通过LayoutInflator获取布局的view.如:

LayoutInflater inflater =(LayoutInflater)            

this.anchor.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

View textEntryView =  inflater.inflate(R.layout.paopao_alert_dialog, null);

       

2、显示位置,可以有很多方式设置显示方式

pop.showAtLocation(findViewById(R.id.ll2), Gravity.LEFT, 0, -90);

或者

pop.showAsDropDown(View anchor, int xoff, int yoff)

 

3、进出场动画

pop.setAnimationStyle(R.style.PopupAnimation);

 

4、点击PopupWindow区域外部,PopupWindow消失

   this.window = new PopupWindow(anchor.getContext());

 

this.window.setTouchInterceptor(new OnTouchListener() {

@Override

public boolean onTouch(View v, MotionEvent event) {

if(event.getAction() ==MotionEvent.ACTION_OUTSIDE) {              

BetterPopupWindow.this.window.dismiss();

return true;

}

return false;

}

});



PopupWindow 自适应宽度实例


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        
     int maxWidth = meathureWidthByChilds() + getPaddingLeft() + getPaddingRight(); super.onMeasure(MeasureSpec.makeMeasureSpec(maxWidth,MeasureSpec.EXACTLY),heightMeasureSpec);   
}   
   public int meathureWidthByChilds() {     
      int maxWidth = 0;    
      View view = null;
      for (int i = 0; i < getAdapter().getCount(); i++) {
           view = getAdapter().getView(i, view, this);     
           view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);     
      if (view.getMeasuredWidth() > maxWidth){        
         maxWidth = view.getMeasuredWidth();    
      }       
  }       
   return maxWidth;  
}



PopupWindow自适应布局

Android自带的Menu菜单,常常无法满足我们的需求,所以就只有自己写menu菜单,通常的选择是用PopupWindow来实现自定义的menu菜单,先看代码,再来说明要注意的几点:

    View menuView = inflater.inflate(R.layout.menu_popwindow, null);  
    final PopupWindow p = new PopupWindow(mContext);  
    p.setContentView(menuView);  
    p.setWidth(ViewGroup.LayoutParams.FILL_PARENT);  
    p.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);  
    p.setAnimationStyle(R.style.MenuWindow);  
    p.setOnDismissListener(this);  
    p.setOutsideTouchable(false);  
    p.setBackgroundDrawable(new ColorDrawable(android.R.color.transparent));  
    p.setFocusable(true); // 如果把焦点设置为false,则其他部份是可以点击的,也就是说传递事件时,不会先走PopupWindow  
      
    mPopwindow = p;  

来说明其中的几点:

  1. 为了让PopupWindow自适应屏幕的宽度,设置宽度时用ViewGroup.LayoutParams.FILL_PARENT,为了自适应子布局的高度,设置高度时用ViewGroup.LayoutParams.WRAP_CONTENT

  2. 由于PopupWindow类没有继承ViewGroup类,所以inflater.inflate(int resource, ViewGroup root)方法的第二个参数只能传为null,传null会使最外层布局的android:layout_xxx都不起作用。所以高度是以第二层布局为主

  3. 为了设置背景和边距,其背景只能设置在第二层布局里,因第一层布局的android:layout_marginXxx不起作用,而设置android:padding_Xxx不会影响背景。

  4. menu有一个特点,就是点外部,menu菜单要消失,要实现这个,有几个属性要一起设置:p.setOutsideTouchable(false);p.setBackgroundDrawable();p.setFocusable(true);

本文我们先来看看android如何通过子线程来实现动画的实例,然后我们再详细讲讲Android 动画实现的原理,这样才能深入浅出。

android通过子线程来实现动画的实例


Android动画,一般是相对原始位置进行参照,本文我们来看看通过子线程修改物体位置实现动画的实例。


布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/show"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="40dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

    <Button
        android:id="@+id/button1"
        android:onClick="MyCLick"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:text="行动" />

    <Button
        android:id="@+id/button2"
        android:onClick="MyCLick"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="28dp"
        android:layout_toLeftOf="@+id/button1"
        android:text="获取位置" />

</RelativeLayout>

动画代码:

public class MainActivity extends Activity {

    TextView textView;
    MyRuns myRuns;

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

        textView = (TextView) findViewById(R.id.show);

        
        myRuns=new MyRuns(new MyHead(textView, 4, 8), true);//位移动画
    }

    static class MyHead extends Handler {// 坐标动画

        View view;// 操作元素
        float cx;
        float cy;

        public MyHead(View view, float cx, float cy) {
            super();
            this.view = view;
            this.cx = cx;
            this.cy = cy;
        }

        @Override
        public void handleMessage(Message msg) {
            // 更新ui
            view.setX(view.getX() + cx);
            view.setY(view.getY() + cy);
            super.handleMessage(msg);
        }

    }

    // 子线程更新位置
    class MyRuns implements Runnable {//更新UI界面

        MyHead head;
        boolean isFire = false;

        public MyRuns(MyHead head, boolean isFire) {
            super();
            this.head = head;
            this.isFire = isFire;
        }

        public boolean isFire() {
            return isFire;
        }

        public void setFire(boolean isFire) {
            this.isFire = isFire;
        }

        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                while (true) {
                    if (!isFire) {
                        break;//停止动画
                    }
                    Thread.sleep(80);
                    Message message = new Message();
                    message.what = 3;
                    message.obj = "";
                    head.sendMessage(message);
                }
            } catch (Exception e) {
                // TODO: handle exception
            }
        }

    }
    
    //开始运动
    void StartThreed(MyRuns myRuns){
        myRuns.setFire(true);//开启
        new Thread(myRuns).start();
    }

    public void MyCLick(View view) {
        if (view.getId() == R.id.button1) {
            StartThreed(myRuns);
        } else if (view.getId() == R.id.button2) {
            myRuns.setFire(false);//结束子线程
            Toast.makeText(getApplicationContext(),
                    "坐标" + textView.getX() + "||" + textView.getY(),
                    Toast.LENGTH_SHORT).show();
        }

    }

}




Android 动画原理

Android 平台提供了一套完整的动画框架,使得开发者可以用它来开发各种动画效果。Android 动画框架详解由原理篇和实例篇两部分组成。本文是第一部分原理篇,主要分析 Tween 动画的实现原理, 最后简单介绍在 Android 中如何通过播放 Gif 文件来实现动画。第二部分实例篇将在原理篇的基础上,向您展示一个动画实例的实现。

 Android 平台提供了一套完整的动画框架,使得开发者可以用它来开发各种动画效果,本文将向读者阐述 Android 的动画框架是如何实现的。 任何一个框架都有其优势和局限性,只有明白了其实现原理,开发者才能知道哪些功能可以利用框架来实现,哪些功能须用其他途径实现。Android 平台提供了两类动画,一类是 Tween 动画,即通过对场景里的对象不断做图像变换 ( 平移、缩放、旋转 ) 产生动画效果;第二类是 Frame 动画,即顺序播放事先做好的图像,跟电影类似。本文是由两部分组成的有关 Android 动画框架详解的第一部分原理篇, 主要分析 Tween 动画的实现原理, 最后简单介绍在 Android 中如何通过播放 Gif 文件来实现动画。我们先看一下动画示例来一点感性认识。

Android 动画使用示例

使用动画示例程序的效果是点击按钮,TextView 旋转一周。读者也可以参看 Apidemos 中包 com.example.android.apis.animationview 下面的 Transition3d 和 com.example.android.apis.view 下面的 Animation1/Animation2/Animation3 示例代码。

清单 1. 代码直接使用动画

 package com.ray.animation;   
import android.app.Activity;   
import android.os.Bundle;   
import android.view.View;   
import android.view.View.OnClickListener;   
import android.view.animation.AccelerateDecelerateInterpolator;   
import android.view.animation.Animation;   
import android.view.animation.RotateAnimation;   
import android.widget.Button;   
 
public class TestAnimation extends Activity implements OnClickListener  
{   
    public void onCreate(Bundle savedInstanceState)  
    {   
        super.onCreate(savedInstanceState);   
        setContentView(R.layout.main);   
        Button btn =(Button)findViewById(R.id.Button);   
        btn.setOnClickListener(this);   
    }  
 
    public void onClick(View v)  
    {   
        Animation anim=null;   
        anim=new?RotateAnimation(0.0f,+360.0f);   
       anim.setInterpolator(new AccelerateDecelerateInterpolator());   
       anim.setDuration(3000);   
       findViewById(R.id.TextView01).startAnimation(anim);   
    }   
}

 使用 XML 文件方式,在打开 Eclipse 中新建的 Android 工程的 res 目录中新建 anim 文件夹,然后在 anim 目录中新建一个 myanim.xml( 注意文件名小写 ),内容如下 :


图 1. 使用 xml 文件方式

 <?xml version="1.0" encoding="utf-8"?>  
 <set xmlns:android="http://schemas.android.com/apk/res/android">  
 <rotate   
    android:interpolator="@android:anim/acclerate_decelerate_interpolator"  
    android:formDegress="0"  
    android:toDegress="+360"  
    android:duration="3000" />  
      
<!--rotate 旋转动画效果  
    属性:  
   interpolator 指定一个动画的插入器,用来控制动画的速度变化  
   fromDegress  动画起始时物件的角度  
   toDegress    动画结束时物件的旋转角度,正代表顺时针  
   duration     动画的持续时间,以毫秒为单位-->  
 
/set>

其中的 java 代码如下:

 package com.ray.animation;   
import android.app.Activity;   
import android.os.Bundle;   
import android.view.View;   
import android.view.View.OnClickListener;   
import android.view.animation.Animation;   
import android.view.animation.AnimationUtils;   
import android.widget.Button;   
import android.widget.TextView;   
public class TestAnimation extends Activity implements OnClickListener  
{   
   public void onCreate(Bundle savedInstanceState)  
   {   
       super.onCreate(savedInstanceState);   
       setContentView(R.layout.main);   
       Button btn =(Button)findViewById(R.id.Button01);   
       btn.setOnClickListener(this);   
   }   
 
   @Override   
   public void onClick(View v)  
   {   
      Animation anim=AnimationUtils.loadAnimation(this,R.anim.my_rotate_action);   
    findViewById(R.id.TextView01).startAnimation(anim);   
   }   
}


Android 动画框架原理

现有的 Android 动画框架是建立在 View 的级别上的,在 View 类中有一个接口 startAnimation 来使动画开始,startAnimation 函数会将一个 Animation 类别的参数传给 View,这个 Animation 是用来指定我们使用的是哪种动画,现有的动画有平移,缩放,旋转以及 alpha 变换等。如果需要更复杂的效果,我们还可以将这些动画组合起来,这些在下面会讨论到。

要了解 Android 动画是如何画出来的,我们首先要了解 Android 的 View 是如何组织在一起,以及他们是如何画自己的内容的。每一个窗口就是一棵 View 树,下面以我们写的 android_tabwidget_tutorial.doc 中的 tab 控件的窗口为例,通过 android 工具 hierarchyviewer 得到的窗口 View Tree 如下图 1 所示:

图 2. 界面 View 结构图


界面 View 结构图

图 3. 界面 View 结构和显示对应图


界面 View 结构和显示对应图图

其实这个图不是完整的,没有把 RootView 和 DecorView 画出来,RootView 只有一个孩子就是 DecorView,这里整个 View Tree 都是 DecorView 的子 View,它们是从 android1.5/frameworks/base/core/res/res/layout/screen_title.xml 这个 layout 文件 infalte 出来的,感兴趣的读者可以参看 frameworks\policies\base\phone\com\android\internal\policy\Imp\PhoneWindow.java 中 generateLayout 函数部分的代码。我们可以修改布局文件和代码来做一些比较 cool 的事情,如象 Windows 的缩小 / 关闭按钮等。标题窗口以下部分的 FrameLayou 就是为了让程序员通过 setContentView 来设置用户需要的窗口内容。因为整个 View 的布局就是一棵树,所以绘制的时候也是按照树形结构遍历来让每个 View 进行绘制。ViewRoot.java 中的 draw 函数准备好 Canvas 后会调用 mView.draw(canvas),其中 mView 就是调用 ViewRoot.setView 时设置的 DecorView。然后看一下 View.java 中的 draw 函数:

递归的绘制整个窗口需要按顺序执行以下几个步骤:

绘制背景;

如果需要,保存画布(canvas)的层为淡入或淡出做准备;

绘制 View 本身的内容,通过调用 View.onDraw(canvas) 函数实现,通过这个我们应该能看出来 onDraw 函数重载的重要性,onDraw 函数中绘制线条 / 圆 / 文字等功能会调用 Canvas 中对应的功能。下面我们会 drawLine 函数为例进行说明;

绘制自己的孩子(通常也是一个 view 系统),通过 dispatchDraw(canvas) 实现,参看 ViewGroup.Java 中的代码可知,dispatchDraw->drawChild->child.draw(canvas) 这样的调用过程被用来保证每个子 View 的 draw 函数都被调用,通过这种递归调用从而让整个 View 树中的所有 View 的内容都得到绘制。在调用每个子 View 的 draw 函数之前,需要绘制的 View 的绘制位置是在 Canvas 通过 translate 函数调用来进行切换的,窗口中的所有 View 是共用一个 Canvas 对象

如果需要,绘制淡入淡出相关的内容并恢复保存的画布所在的层(layer)

绘制修饰的内容(例如滚动条),这个可知要实现滚动条效果并不需要 ScrollView,可以在 View 中完成的,不过有一些小技巧,具体实现可以参看我们的 TextViewExample 示例代码

当一个 ChildView 要重画时,它会调用其成员函数 invalidate() 函数将通知其 ParentView 这个 ChildView 要重画,这个过程一直向上遍历到 ViewRoot,当 ViewRoot 收到这个通知后就会调用上面提到的 ViewRoot 中的 draw 函数从而完成绘制。View::onDraw() 有一个画布参数 Canvas, 画布顾名思义就是画东西的地方,Android 会为每一个 View 设置好画布,View 就可以调用 Canvas 的方法,比如:drawText, drawBitmap, drawPath 等等去画内容。每一个 ChildView 的画布是由其 ParentView 设置的,ParentView 根据 ChildView 在其内部的布局来调整 Canvas,其中画布的属性之一就是定义和 ChildView 相关的坐标系,默认是横轴为 X 轴,从左至右,值逐渐增大,竖轴为 Y 轴,从上至下,值逐渐增大 , 见下图 :

图 4. 窗口坐标系


窗口坐标系

Android 动画就是通过 ParentView 来不断调整 ChildView 的画布坐标系来实现的,下面以平移动画来做示例,见下图 4,假设在动画开始时 ChildView 在 ParentView 中的初始位置在 (100,200) 处,这时 ParentView 会根据这个坐标来设置 ChildView 的画布,在 ParentView 的 dispatchDraw 中它发现 ChildView 有一个平移动画,而且当前的平移位置是 (100, 200),于是它通过调用画布的函数 traslate(100, 200) 来告诉 ChildView 在这个位置开始画,这就是动画的第一帧。如果 ParentView 发现 ChildView 有动画,就会不断的调用 invalidate() 这个函数,这样就会导致自己会不断的重画,就会不断的调用 dispatchDraw 这个函数,这样就产生了动画的后续帧,当再次进入 dispatchDraw 时,ParentView 根据平移动画产生出第二帧的平移位置 (500, 200),然后继续执行上述操作,然后产生第三帧,第四帧,直到动画播完。具体算法描述如清单 2:


清单 2. 算法

 

dispatchDraw()   
    {   
        ....   
        Animation a = ChildView.getAnimation()   
        Transformation tm = a.getTransformation();   
        Use tm to set ChildView's Canvas;   
        Invalidate();   
        ....   
    }  


 图 5. 平移动画示意图


平移动画示意图

以上是以平移动画为例子来说明动画的产生过程,这其中又涉及到两个重要的类型,Animation 和 Transformation,这两个类是实现动画的主要的类,Animation 中主要定义了动画的一些属性比如开始时间、持续时间、是否重复播放等,这个类主要有两个重要的函数:getTransformation 和 applyTransformation,在 getTransformation 中 Animation 会根据动画的属性来产生一系列的差值点,然后将这些差值点传给 applyTransformation,这个函数将根据这些点来生成不同的 Transformation,Transformation 中包含一个矩阵和 alpha 值,矩阵是用来做平移、旋转和缩放动画的,而 alpha 值是用来做 alpha 动画的(简单理解的话,alpha 动画相当于不断变换透明度或颜色来实现动画),以上面的平移矩阵为例子,当调用 dispatchDraw 时会调用 getTransformation 来得到当前的 Transformation,这个 Transformation 中的矩阵如下:

图 6. 矩阵变换图


矩阵变换图

所以具体的动画只需要重载 applyTransformation 这个函数即可,类层次图如下:

图 7. 动画类继承关系图


动画类继承关系图

用户可以定义自己的动画类,只需要继承 Animation 类,然后重载 applyTransformation 这个函数。对动画来说其行为主要靠差值点来决定的,比如,我们想开始动画是逐渐加快的或者逐渐变慢的,或者先快后慢的,或者是匀速的,这些功能的实现主要是靠差值函数来实现的,Android 提供了 一个 Interpolator 的基类,你要实现什么样的速度可以重载其函数 getInterpolation,在 Animation 的 getTransformation 中生成差值点时,会用到这个函数。

从上面的动画机制的分析可知某一个 View 的动画的绘制并不是由他自己完成的而是由它的父 view 完成,所有我们要注意上面 TextView 旋转一周的动画示例程序中动画的效果并不是由 TextView 来绘制的,而是由它的父 View 来做的。findViewById(R.id.TextView01).startAnimation(anim) 这个代码其实是给这个 TextView 设置了一个 animation,而不是进行实际的动画绘制,代码如下 :

 public void startAnimation(Animation animation)   
    {   
      
        animation.setStartTime(Animation.START_ON_FIRST_FRAME);   
      
        setAnimation(animation); invalidate();   
      
    } 

 

其他的动画机制的代码感兴趣的读者请自己阅读,希望通过原理的讲解以后看起来会轻松点,呵呵。

以上就是 Android 的动画框架的原理,了解了原理对我们的开发来说就可以清晰的把握动画的每一帧是怎样生成的,这样便于开发和调试。它把动画的播放 / 绘制交给父 View 去处理而不是让子 View 本身去绘制,这种从更高的层次上去控制的方式便于把动画机制做成一个易用的框架,如果用户要在某个 view 中使用动画,只需要在 xml 描述文件或代码中指定就可以了,从而把动画的实现和 View 本身内容的绘制(象 TextView 里面的文字显示)分离开了,起到了减少耦合和提高易用性的效果。

动画实现示例

在这个例子中,将要实现一个绕 Y 轴旋转的动画,这样可以看到 3D 透视投影的效果,代码如下 ( 清单 4):

清单 3. 实现一个绕 Y 轴旋转的动画


 package com.example.android.apis.animation;   
import android.view.animation.Animation;   
import android.view.animation.Transformation;   
import android.graphics.Camera;   
import android.graphics.Matrix;   
/**  
* An animation that rotates the view on the Y axis between two specified angles.  
* This animation also adds a translation on the Z axis (depth) to improve the effect.  
*/   
public class Rotate3dAnimation extends Animation   
{   
   private final float mFromDegrees;   
   private final float mToDegrees;   
   private final float mCenterX;   
   private final float mCenterY;   
   private final float mDepthZ;   
   private final boolean mReverse;   
   private Camera mCamera;   
   /**  
    * Creates a new 3D rotation on the Y axis. The rotation is defined by its  
    * start angle and its end angle. Both angles are in degrees. The rotation  
    * is performed around a center point on the 2D space, definied by a pair  
    * of X and Y coordinates, called centerX and centerY. When the animation  
    * starts, a translation on the Z axis (depth) is performed. The length  
    * of the translation can be specified, as well as whether the translation  
    * should be reversed in time.  
    *  
    * @param fromDegrees the start angle of the 3D rotation  
    * @param toDegrees the end angle of the 3D rotation  
    * @param centerX the X center of the 3D rotation  
    * @param centerY the Y center of the 3D rotation  
    * @param reverse true if the translation should be reversed, false otherwise  
    */   
   public Rotate3dAnimation(float fromDegrees, float toDegrees,   
                 float centerX,       float centerY,  
                 float depthZ,       boolean reverse)   
   {   
       mFromDegrees = fromDegrees;   
       mToDegrees = toDegrees;   
       mCenterX = centerX;   
       mCenterY = centerY;   
       mDepthZ = depthZ;   
       mReverse = reverse;   
   }   
 
   @Override   
   public void initialize(int width, int height, int parentWidth, int parentHeight)   
   {   
       super.initialize(width, height, parentWidth, parentHeight);   
       mCamera = new Camera();   
   }   
 
   @Override   
   protected void applyTransformation(float interpolatedTime, Transformation t)  
   {   
       final float fromDegrees = mFromDegrees;   
       float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);   
       final float centerX = mCenterX;   
       final float centerY = mCenterY;   
       final Camera camera = mCamera;   
       final Matrix matrix = t.getMatrix();   
       camera.save();   
         
       if (mReverse)  
       {   
           camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);   
       }   
       else   
       {   
           camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));   
       }   
         
       camera.rotateY(degrees);   
       camera.getMatrix(matrix);   
       camera.restore();   
       matrix.preTranslate(-centerX, -centerY);   
       matrix.postTranslate(centerX, centerY);   
   }   
}


在这个例子中我们重载了 applyTransformation 函数,interpolatedTime 就是 getTransformation 函 数传下来的差值点,在这里做了一个线性插值算法来生成中间角度:float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); Camera 类是用来实现绕 Y 轴旋转后透视投影的,我们只需要其返回的 Matrix 值 , 这个值会赋给 Transformation 中的矩阵成员,当 ParentView 去为 ChildView 设置画布时,就会用它来设置坐标系,这样 ChildView 画出来的效果就是一个绕 Y 轴旋转同时带有透视投影的效果。利用这个动画便可以作出像立体翻页等比较酷的效果。如何使用这个 animation 请见 ApiDemos 程序包 com.example.android.apis.animation 中的 Transition3d.java 代码。


Android 中显示 Gif 格式图

有关这一部分,本文将不做详细介绍。 感兴趣的读者请参看 Apidemos 中 com.example.android.apis.graphics 下面的 BitmapDecode.java 中的示例代码。

这里先简单说明一下,它的实现是通过 Movie 这个类来对 Gif 文件进行读取和解码的,同时在 onDraw 函数中不断的绘制每一帧图片完成的,这个示例代码在 onDraw 中调用 invalidate 来反复让 View 失效来让系统不断调用 SampleView 的 onDraw 函数;至于选出哪一帧图片进行绘制则是传入系统当前时间给 Movie 类,然后让它根据时间顺序来选出帧图片。反复让 View 失效的方式比较耗资源,绘制效果允许的话可以采取延时让 View 失效的方式来减小 CPU 消耗。

目前使用这个方式播放一些 Gif 格式的动画时会出现花屏的现象,这是因为 Android 中使用的 libgif 库是比较老的版本,新的 tag 不支持,所以导致花屏,解决办法有制作 Gif 图片时别使用太新的 tag 或完善 android 中对应的 libgif 库。


结束语

本文介绍了 Android 动画框架的基本原理,可以帮助开发者深入理解 Android 的动画是如何实现的,从而能够充分利用 android 现有框架来做出够眩、够酷的动画效果。


本文主要讲的内容是Android5.0L因SystemUI ANR导致的黑屏的问题现象、解决方案、初步分析、深入分析问题、及相关问题等,如果你在做Android开发的时候也遇到这种现象,可以参考一下本文。

本文我们来讲讲Android5.0L因SystemUI ANR导致的黑屏问题分析


一、问题现象

用户直观看到的现象是黑屏。出问题时StatusBar、NavigationBar和墙纸消失。大部分发生在FOTA重启之后,出现概率很低。

Platform:MSM8916

Android版本:5.0.2L

BuildType:user

系统软件版本:VA6V+L5V0

系统RAM:1GB

参考机行为:

1、5.0L的Nexus4和5.1L的Nexus5都没有重现此问题。

二、解决方案

通过初步分析、深入分析(具体分析过程、关键代码和log在下面会附上)我们清楚的知道了问题发生的原因:

1、开机初始化的过程中需要获取camera的相关参数,获取的过程中会以api级别打开camera(用户不可见的形式打开)然后快速关闭

2、在打开的过程中会开启一个Thermal deamon 线程进行thermal相关的处理,然后关闭时会等待这个thermal deamon线程退出

3、这个线程开启的时候会通过异步的方式执行一次thermal相关的处理,并等待结果返回, 执行的方式是多线程异步处理

在当前代码的执行状态下有一定概率(很小,只有开机或者重启时走这个流程)出现因为调度原因而先执行了closecamera的操作并先删除了异步处理结果的链表,然后等待thermal deamon线程退出,从而导致thermal deamon被唤醒时异步处理结果的链表已经被删除而出现死结。一旦死结产生,SystemUI就会ANR,然后依附于SystemUI的statusbar和navigationbar以及imagewallpaper都会被阻塞,一旦SystemUI进程被Kill,这些组件都会消失,产生黑屏现象。

针对以上问题的根本原因,我们给出以下解决方案:

1、修正代码的处理顺序

Closecamera 时先执行m_thermalAdapter.deinit等待thermal deamon线程将结果处理完成退出并返回,再free all pending api results,因为m_thermalAdapter.deinit会依赖pending api results,这同样是遵循初始化和反初始化的栈原则,即opencamera时最后初始化的是依赖别人最多的,但是不被别人依赖,因此 closecamera时需要先反初始化在opencamera时最后初始化的,按照栈的方式原则处理。

2、方案相关的具体代码和log





以上是发生死锁时锁对应的log以及相应代码和调用栈,通过红线圈住部分我们可以看到发生问题时的关键调用关系和状态,同时也给出了代码处理顺序有问题的地方。

3、最终方案的代码修改


三、问题初步分析

以Idol347出问题时候的一份典型trace和log为例,发现出问题时SystemUI的主线程block在了一个向CameraService发起的Binder调用中,从而导致SystemUI

的后续事件TimeOut引起ANR,主线程的具体trace如下:


然后继续追踪CameraService的服务端的trace,发现/system/bin/mediaserver中的处理CameraService的 Binder线程也被block了,然后追踪trace中各个线程的调用栈和互斥锁的使用,发现处理调用CameraService的 addlistener的Binderthread之所以被阻塞,是因为另外一个binder thread先占用了锁,然后在占用的过程中去注册thermal回调,但是注册的过程需要占用另外一个锁,但是这个锁被第三个thread在注销thermal回调的时候先占用,并且join另外一个thread,因此整个依赖环需要另外一个thread退出才能解,从当前的问题现象来看,这个thread不会太快退出,所以导致了ANR和黑屏问题。

通过初步分析我们发现的问题:

是否需要占用着锁的情况下去join另外一个thread,或者这种状态是否合理?


具体的调用栈和代码中锁的关系如下:











四、深入分析问题

经过初步我们定位到了第一个问题点,同时也产生了1个问题,接下来我们继续深入分析以期能到找到答案和问题的根本原因。

1、是否需要占用着锁的情况下去join另外一个thread,或者这种状态是否合理?

通过进一步分析和查看代码发现,Join的另外一个thread不能很快退出是因为它在执行callback时等待另外一个条件的满足,具体逻辑调用关系如下:





另外一个条件之所以不满足的原因:

开机初始化的过程中需要获取camera的相关参数,获取的过程中会以api级别打开camera(用户不可见的形式打开)然后快速关闭,在打开的过程中会开启一个Thermal deamon 线程进行thermal相关的处理,然后关闭时会等待这个thermal deamon线程退出,但是这个线程开启的时候会通过异步的方式执行一次thermal相关的处理,并等待结果返回,由于是多线程的异步处理,在当前代码的执行状态下就有一定概率(很小)出现因为调度原因而先执行了closecamera的操作并先删除了异步处理结果的链表,然后等待thermal deamon线程退出,从而导致thermal deamon被唤醒时异步处理结果的链表已经被删除而出现死结。

一旦死结产生,SystemUI就会ANR,然后依附于SystemUI的statusbar和navigationbar以及imagewallpaper都会被阻塞,一旦SystemUI进程被Kill,这些组件都会消失,产生黑屏现象。

五、其他相关问题

为什么大部分发生在FOTA升级之后?

由于这个问题是发生在启动或者重启时,而且只有这个过程才会有很小概率触发。而FOTA之后会自动重启,所以就有概率触发这个问题,由于用户平时使用中很少重启,所以概率不高。


appBase是一个Android app开发的基础集合,这样的目的可以让我们的Android app开发得更快速,本文我们来看看用appBase如何快速开发购物车

appBase基础介绍

appBase是什么?

appBase是一个Android app开发的基础集合,目的是任何应用都可以在这个基础之上开发app,省去了搭建框架的时间。

appBase=xutils+fastjson+avlib

    xutils使用了其中HttpUtils、BitmapUtils、DbUtils
    fastjson使用了json解析
    avlib大家比较陌生,这个库是我另外一个简单的工具库。主要功能是View的自动绑定、View的常用数据自动绑定、万能Adapter等

目的:是为了让懂java的同学能够快速上手Android开发。


一、看看框架结构

01.jpeg

- apicloud.sdk是对apicloud的云API的调用做了简单封装
- base:只包含BaseActivity
- http:基于HttpUtils简化了常用的网络请求,定义网络参数APIs的配置
- presenter:采用了MVP中的P来命名,可以让非UI处理业务抽出放到这个结构中,因此BasePresenter诞生了。
- util:常用的工具类
- widget:常用的自定义组件(待扩展)
- Application:继承android.app.Application,为了统一使用框架中的组件对象,避免了组件的重复创建。因此建议使用这个类配置application的name。当然也可以基于此类扩展。


二、创建一个新项目

    第一步:创建一个空的Android project
    技术分享
    注意:删除自动添加的android-support-v4.jar(appBase中包含有)
    第二步:引用appBase
    技术分享

    第三步:修改AndroidManifest.xml

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.snicesoft.appbase.demo"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="21" />

    <application
        android:allowBackup="true"
        android:name="com.snicesoft.Application"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
    </application>
    </manifest>

    添加:android:name=”com.snicesoft.Application”

    第四步:创建Activity

    package com.snicesoft.appbase.demo;
    import com.snicesoft.avlib.annotation.Layout;
    import com.snicesoft.avlib.rule.IData;
    import com.snicesoft.avlib.rule.IHolder;
    import com.snicesoft.base.BaseActivity;
    @Layout(R.layout.activity_main)
    public class MainActivity extends
        BaseActivity<MainActivity.Holder, MainActivity.Data> {
        public class Holder extends IHolder {

            @Override
            public void initViewParams() {

            }

        }

        public class Data extends IData {

        }

        @Override
        public Data newData() {
            return new Data();
        }

        @Override
        public Holder newHolder() {
            return new Holder();
        }
    }

    看着class一栏,大家可能会花了眼,怎么这么长。这只是一种写法,推荐的写法(内部类)。我来说明下这个类:
    IHolder是指View自动绑定的容器
    IData是指View的数据自动绑定容器

    第五步:使用IHolder和IData

    package com.snicesoft.appbase.demo;
    import com.snicesoft.avlib.annotation.DataBind;
    import com.snicesoft.avlib.annotation.Id;
    import com.snicesoft.avlib.annotation.Layout;
    import com.snicesoft.avlib.rule.IData;
    import com.snicesoft.avlib.rule.IHolder;
    import com.snicesoft.base.BaseActivity;
    @Layout(R.layout.activity_main)
    public class MainActivity extends
        BaseActivity<MainActivity.Holder, MainActivity.Data> {
        public class Holder extends IHolder {
            @Id(R.id.textView1)
            TextView textView1;
            @Id(R.id.button1)
            Button button1;
            @Override
            public void initViewParams() {

            }

        }

        public class Data extends IData {
            @DataBind(id = R.id.textView1)
            String tv1 = "我是自动绑定的TextView";
            @DataBind(id = R.id.button1)
            String btn1 = "我是自动绑定的Button";
        }

        @Override
        public Data newData() {
            return new Data();
        }

        @Override
        public Holder newHolder() {
            return new Holder();
        }
    }

    运行结果

02.png


Android appBase 购物车开发


购物车,在商城app中是必不可少的一部分,也是使用的比较多的,这里简单的做一个效果。

先来看看效果图

这里写图片描述


1、创建项目

第一种、引用appBase项目即可
第二种、将appBase的jar文件copy到libs下

03.jpeg

我用的第二种,如上图所示。


2、代码生成

通过代码生成器生成Activity、Presenter、Adapter

1、生成Activity(默认生成Presenter)

01.jpeg
2、生成Adapter

01.jpeg
3、网络请求数据

这里网络数据使用的是APICloud,那么就需要对于APICloudSDK进行配置。最新的appBase讲配置放了出来,只要在applcation中进行代码配置就可以了。


package com.example.shopcartdemo;

import com.apicloud.sdk.APICloudSDK;
import com.snicesoft.Application;
import com.snicesoft.http.HttpReq;

public class MyApplication extends Application {
    final String APP_ID = "A6960031839242";
    final String APP_KEY = "3F248D5F-50DB-782A-F437-E13796238B9E";

    @Override
    public void onCreate() {
        super.onCreate();
        APICloudSDK.getInstance().init(APP_ID, APP_KEY);
        APICloudSDK.getInstance().init(hu());
        HttpReq.debug = true;
    }
}


这里添加了DialogPresenter,作用就是为了请求的时候对dialog的控制

DialogPresenter


package com.example.shopcartdemo.presenter;

import android.app.ProgressDialog;
import android.content.Context;

import com.snicesoft.presenter.BasePresenter;
import com.snicesoft.util.DialogUtil;

public class DialogPresenter<C extends BasePresenter.Callback> extends
        BasePresenter<C> {

    public DialogPresenter(Context context) {
        super(context);
        progressDialog = DialogUtil.getProgressDialog(context);
    }

    ProgressDialog progressDialog;

    protected void showDialog(CharSequence message, boolean... flag) {
        if (flag != null) {
            if (flag.length > 0)
                progressDialog.setCancelable(flag[0]);
            if (flag.length > 1)
                progressDialog.setCanceledOnTouchOutside(flag[1]);
        }
        progressDialog.setMessage(message);
        if (!progressDialog.isShowing())
            progressDialog.show();
    }

    protected void closeDialog() {
        if (progressDialog.isShowing())
            progressDialog.dismiss();
    }
}


MainPresenter


package com.example.shopcartdemo.presenter;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;

import com.alibaba.fastjson.JSON;
import com.apicloud.sdk.APICloudSDK;
import com.example.shopcartdemo.adapter.ShopCartAdapter;
import com.lidroid.xutils.exception.HttpException;
import com.snicesoft.http.HttpCallback;
import com.snicesoft.presenter.BasePresenter;
import com.snicesoft.util.CommonUtils;

public class MainPresenter extends DialogPresenter<MainPresenter.Callback> {

    public MainPresenter(Context context) {
        super(context);
    }

    public interface Callback extends BasePresenter.Callback {
        void setShopCartList(List<ShopCartAdapter.Data> list);
    }

    public static class ShopCart {
        String title;
        int price;
        int count;

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public int getPrice() {
            return price;
        }

        public void setPrice(int price) {
            this.price = price;
        }

        public int getCount() {
            return count;
        }

        public void setCount(int count) {
            this.count = count;
        }

    }

    public void getShopCartList() {
        showDialog("正在加载");
        APICloudSDK
                .getInstance()
                .GET("/mcm/api/ShopCart?filter=%7B%22where%22%3A%7B%7D%2C%22skip%22%3A0%2C%22limit%22%3A20%7D",
                        null, new HttpCallback() {

                            @Override
                            public void onSuccess(String result) {
                                closeDialog();
                                List<ShopCart> array = JSON.parseArray(result,
                                        ShopCart.class);
                                List<ShopCartAdapter.Data> list = new ArrayList<ShopCartAdapter.Data>();
                                for (ShopCart cart : array) {
                                    list.add(new ShopCartAdapter.Data(
                                            0,
                                            cart.title,
                                            cart.price,
                                            "http://ck.haier.com/UpLoad/2015-05-15/a5e8cac4-2671-4aa0-83a7-66c64e051f95.jpg",
                                            cart.count));
                                }
                                if (callback != null)
                                    callback.setShopCartList(list);
                            }

                            @Override
                            public void onFailure(HttpException arg0) {
                                closeDialog();
                                CommonUtils.showToast(getContext(), "请求失败,请稍后重试");
                            }
                        });
    }
}



在这个类中,网络请求和网络解析实体对象都用内部类来定义,注意:内部类定义一定要用static class,否则fastjson无法正常解析,会导致无法反射创建对象。

4、数据绑定和交互

首先看下Activity


package com.example.shopcartdemo;

import java.util.List;

import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.CheckBox;
import android.widget.ListView;
import android.widget.TextView;

import com.example.shopcartdemo.adapter.ShopCartAdapter;
import com.example.shopcartdemo.adapter.ShopCartAdapter.ViewCallback;
import com.example.shopcartdemo.presenter.MainPresenter;
import com.snicesoft.avlib.annotation.DataBind;
import com.snicesoft.avlib.annotation.DataType;
import com.snicesoft.avlib.annotation.Id;
import com.snicesoft.avlib.annotation.Layout;
import com.snicesoft.avlib.rule.IData;
import com.snicesoft.avlib.rule.IHolder;
import com.snicesoft.base.BaseActivity;

@Layout(R.layout.activity_main)
public class MainActivity extends
        BaseActivity<MainActivity.Holder, MainActivity.Data> implements
        MainPresenter.Callback, ViewCallback {
    class Holder extends IHolder {
        @Id(R.id.lvShopCard)
        ListView lvShopCard;
        @Id(R.id.cbAll)
        CheckBox cbAll;
        @Id(R.id.tvPrice)
        TextView tvPrice;

        @Override
        public void initViewParams() {
            cbAll.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    _data.shopCartAdapter.setAll(cbAll.isChecked());
                    tvPrice.setText("¥" + _data.shopCartAdapter.calc());
                }
            });
        }
    }

    class Data extends IData {
        @DataBind(id = R.id.lvShopCard, dataType = DataType.ADAPTER)
        ShopCartAdapter shopCartAdapter = new ShopCartAdapter(getBaseContext());
    }

    @Override
    public Holder newHolder() {
        return new Holder();
    }

    @Override
    public Data newData() {
        return new Data();
    }

    MainPresenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        presenter = new MainPresenter(this);
        presenter.setCallback(this);
        _data.shopCartAdapter.setCallback(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        presenter.getShopCartList();
    }

    @Override
    public void onClick(View v) {
        super.onClick(v);
        switch (v.getId()) {
        case R.id.btnGoPay:
            break;
        case R.id.btnDelete:
            break;
        default:
            break;
        }
    }

    boolean isNormal = true;

    @Override
    public void setShopCartList(List<ShopCartAdapter.Data> list) {
        _data.shopCartAdapter.setDataList(list);
        refreshView();
    }

    @Override
    public void refreshView() {
        _holder.cbAll.setChecked(_data.shopCartAdapter.isAll());
        _holder.tvPrice.setText("¥" + _data.shopCartAdapter.calc());
    }

}


基本的Holder和Data将组件和数据进行简单的管理,清晰可见。
presenter将业务进行分离,将传统的activity中请求数据进行分离。
ViewCallback这个类是为了解决Adapter与Activity直接的交互定义的接口。

接下来看看Adapter


package com.example.shopcartdemo.adapter;

import android.content.Context;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;

import com.example.shopcartdemo.R;
import com.snicesoft.avlib.AVLib;
import com.snicesoft.avlib.annotation.DataBind;
import com.snicesoft.avlib.annotation.DataType;
import com.snicesoft.avlib.annotation.Id;
import com.snicesoft.avlib.annotation.Layout;
import com.snicesoft.avlib.rule.IData;
import com.snicesoft.avlib.rule.IHolder;
import com.snicesoft.avlib.view.ViewFinder;
import com.snicesoft.avlib.widget.AvAdapter;

@Layout(R.layout.item_shopcart)
public class ShopCartAdapter extends
        AvAdapter<ShopCartAdapter.Holder, ShopCartAdapter.Data> {

    public ShopCartAdapter(Context context) {
        super(context);
    }

    class Holder extends IHolder {
        @Id(R.id.btnAdd)
        Button btnAdd;
        @Id(R.id.btnDelete)
        Button btnDelete;
        @Id(R.id.cbSelect)
        CheckBox cbSelect;
        @Id(R.id.img)
        ImageView img;

        @Override
        public void initViewParams() {
        }
    }

    public static class Data extends IData {
        long gid;

        boolean isChecked = true;

        public long getGid() {
            return gid;
        }

        @DataBind(id = R.id.tvTitle)
        String title;
        @DataBind(id = R.id.tvPrice, prefix = "¥")
        int price;
        @DataBind(id = R.id.img, dataType = DataType.IMG)
        String image;
        @DataBind(id = R.id.edtCount)
        int count;

        public Data(long gid, String title, int price, String image, int count) {
            super();
            this.gid = gid;
            this.title = title;
            this.price = price;
            this.image = image;
            this.count = count;
        }
    }

    public interface ViewCallback {
        void refreshView();
    }

    ViewCallback callback;

    public void setCallback(ViewCallback callback) {
        this.callback = callback;
    }

    @Override
    public Holder newHolder() {
        return new Holder();
    }

    @Override
    public void bindAfter(int position, final View view, Holder holder,
            final Data data) {
        holder.cbSelect.setOnCheckedChangeListener(null);
        holder.cbSelect.setChecked(data.isChecked);
        holder.btnAdd.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                data.count++;
                AVLib.dataBindTo(data, new ViewFinder(view), "count");
                if (callback != null)
                    callback.refreshView();
            }
        });
        holder.btnDelete.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                if (data.count > 1) {
                    data.count--;
                    AVLib.dataBindTo(data, new ViewFinder(view), "count");
                    if (callback != null)
                        callback.refreshView();
                }
            }
        });

        holder.cbSelect
                .setOnCheckedChangeListener(new OnCheckedChangeListener() {

                    @Override
                    public void onCheckedChanged(CompoundButton buttonView,
                            boolean isChecked) {
                        data.isChecked = isChecked;
                        if (callback != null)
                            callback.refreshView();
                    }
                });

    }

    public void setAll(boolean isChecked) {
        for (Data data : getDataList()) {
            data.isChecked = isChecked;
        }
        notifyDataSetChanged();
    }

    public int calc() {
        int total = 0;
        for (Data data : getDataList()) {
            if (data.isChecked)
                total += data.count * data.price;
        }
        return total;
    }

    public boolean isAll() {
        for (Data data : getDataList()) {
            if (!data.isChecked)
                return false;
        }
        return true;
    }
}



Holder的组件定义原则:需要在当前所在类中调用即可定义
Data的DataBind使用原则:需要绑定的的组件都可以。
当然通过上面可以看出Data中可以定义不用DataBind注解的字段,这个替代了传统的辅助Map或者List解决一些问题。
在上面的Data中用isChecked来表示CheckBox是否选中。这个问题,在我刚开始写android的时候,常常会用Map集合将position对应的boolean值记录下来,然后在getView中去检测。现在可以简单的通过Data几种管理,使得Adapter的字段不需要那么繁琐。

对于isChecked的使用产生了下面3个业务方法
setAll:这个方法用来设置全部选中(Activity中的全选按钮事件)
calc:这个方法用来计算购物车总额(Activity中的总计)
isAll:这个方法用来判断是否全部被选中(Activity的全选按钮设置checked的依据)


开发思路我们就讲得差不多了,做开发,最重要的还是思路,整体代码我们就不给出了,有兴趣的朋友可以自己整理代码,加深印象。

[!--infotagslink--]

相关文章

  • 图解PHP使用Zend Guard 6.0加密方法教程

    有时为了网站安全和版权问题,会对自己写的php源码进行加密,在php加密技术上最常用的是zend公司的zend guard 加密软件,现在我们来图文讲解一下。 下面就简单说说如何...2016-11-25
  • Android子控件超出父控件的范围显示出来方法

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • ps怎么使用HSL面板

    ps软件是现在很多人都会使用到的,HSL面板在ps软件中又有着非常独特的作用。这次文章就给大家介绍下ps怎么使用HSL面板,还不知道使用方法的下面一起来看看。 &#8195;...2017-07-06
  • Plesk控制面板新手使用手册总结

    许多的朋友对于Plesk控制面板应用不是非常的了解特别是英文版的Plesk控制面板,在这里小编整理了一些关于Plesk控制面板常用的使用方案整理,具体如下。 本文基于Linu...2016-10-10
  • 使用insertAfter()方法在现有元素后添加一个新元素

    复制代码 代码如下: //在现有元素后添加一个新元素 function insertAfter(newElement, targetElement){ var parent = targetElement.parentNode; if (parent.lastChild == targetElement){ parent.appendChild(newEl...2014-05-31
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • jQuery 1.9使用$.support替代$.browser的使用方法

    jQuery 从 1.9 版开始,移除了 $.browser 和 $.browser.version , 取而代之的是 $.support 。 在更新的 2.0 版本中,将不再支持 IE 6/7/8。 以后,如果用户需要支持 IE 6/7/8,只能使用 jQuery 1.9。 如果要全面支持 IE,并混合...2014-05-31
  • Android模拟器上模拟来电和短信配置

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • C#注释的一些使用方法浅谈

    C#注释的一些使用方法浅谈,需要的朋友可以参考一下...2020-06-25
  • 使用percona-toolkit操作MySQL的实用命令小结

    1.pt-archiver 功能介绍: 将mysql数据库中表的记录归档到另外一个表或者文件 用法介绍: pt-archiver [OPTION...] --source DSN --where WHERE 这个工具只是归档旧的数据,不会对线上数据的OLTP查询造成太大影响,你可以将...2015-11-24
  • 使用GruntJS构建Web程序之构建篇

    大概有如下步骤 新建项目Bejs 新建文件package.json 新建文件Gruntfile.js 命令行执行grunt任务 一、新建项目Bejs源码放在src下,该目录有两个js文件,selector.js和ajax.js。编译后代码放在dest,这个grunt会...2014-06-07
  • 如何使用php脚本给html中引用的js和css路径打上版本号

    在搜索引擎中搜索关键字.htaccess 缓存,你可以搜索到很多关于设置网站文件缓存的教程,通过设置可以将css、js等不太经常更新的文件缓存在浏览器端,这样访客每次访问你的网站的时候,浏览器就可以从浏览器的缓存中获取css、...2015-11-24
  • MySQL日志分析软件mysqlsla的安装和使用教程

    一、下载 mysqlsla [root@localhost tmp]# wget http://hackmysql.com/scripts/mysqlsla-2.03.tar.gz--19:45:45-- http://hackmysql.com/scripts/mysqlsla-2.03.tar.gzResolving hackmysql.com... 64.13.232.157Conn...2015-11-24
  • 夜神android模拟器设置代理的方法

    夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • Android WebView加载html5页面实例教程

    如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
  • PHP函数分享之curl方式取得数据、模拟登陆、POST数据

    废话不多说直接上代码复制代码 代码如下:/********************** curl 系列 ***********************///直接通过curl方式取得数据(包含POST、HEADER等)/* * $url: 如果非数组,则为http;如是数组,则为https * $header:...2014-06-07
  • C#模拟http 发送post或get请求的简单实例

    下面小编就为大家带来一篇C#模拟http 发送post或get请求的简单实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • 安装和使用percona-toolkit来辅助操作MySQL的基本教程

    一、percona-toolkit简介 percona-toolkit是一组高级命令行工具的集合,用来执行各种通过手工执行非常复杂和麻烦的mysql和系统任务,这些任务包括: 检查master和slave数据的一致性 有效地对记录进行归档 查找重复的索...2015-11-24
  • 使用jquery修改表单的提交地址基本思路

    基本思路: 通过使用jquery选择器得到对应表单的jquery对象,然后使用attr方法修改对应的action 示例程序一: 默认情况下,该表单会提交到page_one.html 点击button之后,表单的提交地址就会修改为page_two.html 复制...2014-06-07