Android自定义View构造函数详解

初始Custom View的构造函数

之前写过一篇实现圆形进度条的博客(自定义圆形进度条),通常我们在实现Custom View的时候,都会先继承View并实现View的三个构造函数,例如:

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;

public class MyCustomView extends View {
 /**
  * 第一个构造函数
  */
 public MyCustomView(Context context) {
  this(context, null);
 }

 /**
  * 第二个构造函数
  */
 public MyCustomView(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
 }

 /**
  * 第三个构造函数
  */
 public MyCustomView(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  // TODO:获取自定义属性
 }

 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
 }
}

网上有很多关于三个构造函数使用时机的说法,但是说法正确的却没有几家,这里正式的给大家科普一下:

在代码中直接new一个Custom View实例的时候,会调用第一个构造函数.这个没有任何争议.
在xml布局文件中调用Custom View的时候,会调用第二个构造函数.这个也没有争议.
在xml布局文件中调用Custom View,并且Custom View标签中还有自定义属性时,这里调用的还是第二个构造函数.
也就是说,系统默认只会调用Custom View的前两个构造函数,至于第三个构造函数的调用,通常是我们自己在构造函数中主动调用的(例如,在第二个构造函数中调用第三个构造函数).

至于自定义属性的获取,通常是在构造函数中通过obtainStyledAttributes函数实现的.这里先介绍一下如何生成Custom View的自定义属性.

生成Custom View的自定义属性

Custom View添加自定义属性主要是通过declare-styleable标签为其配置自定义属性,具体做法是: 在res/values/目录下增加一个resources xml文件,示例如下(res/values/attrs_my_custom_view.xml):

<resources>
 <declare-styleable name="MyCustomView">
  <attr name="custom_attr1" format="string" />
  <attr name="custom_attr2" format="string" />
  <attr name="custom_attr3" format="string" />
  <attr name="custom_attr4" format="string" />
 </declare-styleable>
 <attr name="custom_attr5" format="string" />
</resources>

在上述xml文件中,我们声明了一个自定义属性集MyCustomView,其中包含了custom_attr1,custom_att2,custom_attr3,custom_attr4四个属性.同时,我们还声明了一个独立的属性custom_attr5.

所有resources文件中声明的属性都会在R.attr类中生成对应的成员变量:

public final class R {
 public static final class attr {
  public static final int custom_attr1=0x7f010038;
  public static final int custom_attr2=0x7f010039;
  public static final int custom_attr3=0x7f01003a;
  public static final int custom_attr4=0x7f01003b;
  public static final int custom_attr5=0x7f010000;
 }
}

但是声明在标签中的属性,系统还会在R.styleable类中生成相关的成员变量:

public static final class styleable {
  public static final int[] MyCustomView = {
   0x7f010038, 0x7f010039, 0x7f01003a, 0x7f01003b
  };
  public static final int MyCustomView_custom_attr1 = 0;
  public static final int MyCustomView_custom_attr2 = 1;
  public static final int MyCustomView_custom_attr3 = 2;
  public static final int MyCustomView_custom_attr4 = 3;
}

可以看出,R.styleable.MyCustomView是一个数组,其中的元素值恰好就是R.attr.custom_attr1~R.attr.custom_attr4的值.而下面的MyCustomView_custom_attr1~MyCustomView_custom_attr4正好就是其对应的索引.

知道了这些之后,我们就可以来学习一下,如何在Custom View的构造函数中获取自定义属性的值了.

在Custom View的构造函数中获取自定义属性

在第三个构造函数中获取自定义属性的代码如下:

public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);

 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView);

 String attr1 = ta.getString(R.styleable.MyCustomView_custom_attr1);
 String attr2 = ta.getString(R.styleable.MyCustomView_custom_attr2);
 String attr3 = ta.getString(R.styleable.MyCustomView_custom_attr3);
 String attr4 = ta.getString(R.styleable.MyCustomView_custom_attr4);

 Log.e("customview", "attr1=" + attr1);
 Log.e("customview", "attr2=" + attr2);
 Log.e("customview", "attr3=" + attr3);
 Log.e("customview", "attr4=" + attr4);
 ta.recycle();
 }

关于自定义属性的获取,我们主要是调用了context.obtainStyledAttributes这个函数,相信这个函数大家自定义View的时候都用的很熟练了.我们来看一下这个函数的源码实现:

public final TypedArray obtainStyledAttributes(AttributeSet set, @StyleableRes int[] attrs) {
 return getTheme().obtainStyledAttributes(set, attrs, 0, 0);
}

通过对源码的追踪,我们发现context的两个参数的obtainStyledAttributes方法最终是调用了Theme的4个参数的obtainStyledAttributes方法.我们来看一下这个函数的源码实现:

public TypedArray obtainStyledAttributes(AttributeSet set,
  @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
 final int len = attrs.length;
 final TypedArray array = TypedArray.obtain(Resources.this, len);

 // XXX note that for now we only work with compiled XML files.
 // To support generic XML files we will need to manually parse
 // out the attributes from the XML file (applying type information
 // contained in the resources and such).
 final XmlBlock.Parser parser = (XmlBlock.Parser)set;
 AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
   parser != null ? parser.mParseState : 0, attrs, array.mData, array.mIndices);

 array.mTheme = this;
 array.mXml = parser;

 if (false) {
  int[] data = array.mData;

  System.out.println("Attributes:");
  String s = " Attrs:";
  int i;
  for (i=0; i<set.getAttributeCount(); i++) {
   s = s + " " + set.getAttributeName(i);
   int id = set.getAttributeNameResource(i);
   if (id != 0) {
    s = s + "(0x" + Integer.toHexString(id) + ")";
   }
   s = s + "=" + set.getAttributeValue(i);
  }
  System.out.println(s);
  s = " Found:";
  TypedValue value = new TypedValue();
  for (i=0; i<attrs.length; i++) {
   int d = i*AssetManager.STYLE_NUM_ENTRIES;
   value.type = data[d+AssetManager.STYLE_TYPE];
   value.data = data[d+AssetManager.STYLE_DATA];
   value.assetCookie = data[d+AssetManager.STYLE_ASSET_COOKIE];
   value.resourceId = data[d+AssetManager.STYLE_RESOURCE_ID];
   s = s + " 0x" + Integer.toHexString(attrs[i])
    + "=" + value;
  }
  System.out.println(s);
 }

 return array;
}

这里就不做过多的源码讲解,而是把这四个参数的含义解释给大家:

AttributeSet set: 属性值的集合.

int[] attrs: 我们自定义属性集合在R类中生成的int型数组.这个数组中包含了自定义属性的资源ID.
int defStyleAttr: 这是当前Theme中的包含的一个指向style的引用.当我们没有给自定义View设置declare-styleable资源集合时,默认从这个集合里面查找布局文件中配置属性值.传入0表示不像该defStyleAttr中查找默认值.
int defStyleRes: 这个也是一个指向Style的资源ID,但是仅在defStyleAttr为0或者defStyleAttr不为0但Theme中没有为defStyleAttr属性赋值时起作用.

由于一个属性可以在很多地方对其进行赋值,包括: XML布局文件中、decalare-styleable、theme中等,它们之间是有优先级次序的,按照优先级从高到低排序如下:

属性赋值优先级次序表:

在布局xml中直接定义 > 在布局xml中通过style定义 > 自定义View所在的Activity的Theme中指定style引用 > 构造函数中defStyleRes指定的默认值

为了让大家有更清楚更直观的了解,再接下来设置自定义属性的章节中,我将对custom_attr1~4这4个属性分别在上述四个地方进行定义,然后在Custom View的构造函数中获取它的值,从而看一下,优先级顺序是否和我们预期的一样.

设置自定义属性值

以下的第几个参数均是针对Resources.Theme类的obtainStyledAttributes四参数构造方法来说明的.

第二个参数——在布局xml文件中为属性赋值

在设置自定义属性之前,我们首先要在主Activity的布局文件中调用我们的Custom View,并且为其设置特定的属性.

主布局文件内容如下:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:custom="http://schemas.android.com/apk/res-auto"
 android:id="@+id/container"
 android:layout_width="match_parent"
 android:layout_height="match_parent">

 <com.kevintan.eventbussample.view.MyCustomView
  android:id="@+id/id_custom_view"
  android:layout_width="400dp"
  android:layout_height="400dp"
  custom:custom_attr1="attr1_xml"
  style="@style/TestCustomView"/>

</FrameLayout>

示例结果:

05-28 17:19:56.542 23575-23575/? E/customview: attr1=attr1_xml
05-28 17:19:56.542 23575-23575/? E/customview: attr2=null
05-28 17:19:56.542 23575-23575/? E/customview: attr3=null
05-28 17:19:56.542 23575-23575/? E/customview: attr4=null

注意:

在给自定义属性赋值时,首先需要增加自定义属性的命名空间,例如: xmlns:custom=”http://schemas.Android.com/apk/res-auto”,Android Studio推荐使用res-auto,在Eclipse中需要使用Custom View所在的包名: xmlns:cv=”http://schemas.android.com/apk/com.kevintan.eventbussample.view”
这里,在布局文件中我们为custom_attr1赋值为: attr1_xml.自定义View中获取该属性值对应了getTheme().obtainStyledAttributes方法中的第二个参数@StyleableRes int[] attrs

第二个参数——在style中为属性赋值

其次,自定义属性还可以在Style中进行赋值.

首先,我们在xml布局文件中还为MyCustomView增加一个自定义的style,style代码如下:

<style name="TestCustomView">
 <item name="custom_attr1">attr1_style</item>
 <item name="custom_attr2">attr2_style</item>
</style>

然后,我们修改布局文件,增加style字段:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:custom="http://schemas.android.com/apk/res-auto"
 android:id="@+id/container"
 android:layout_width="match_parent"
 android:layout_height="match_parent">

 <com.kevintan.eventbussample.view.MyCustomView
  android:id="@+id/id_custom_view"
  android:layout_width="400dp"
  android:layout_height="400dp"
  custom:custom_attr1="attr1_xml"
  style="@style/TestCustomView"/>

</FrameLayout>

示例结果:

05-28 17:19:56.542 23575-23575/? E/customview: attr1=attr1_xml
05-28 17:19:56.542 23575-23575/? E/customview: attr2=attr2_style
05-28 17:19:56.542 23575-23575/? E/customview: attr3=null
05-28 17:19:56.542 23575-23575/? E/customview: attr4=null

这里我们再次对custom_attr1属性进行了赋值,同时我们对custom_attr2也进行了赋值.

小提示:

聪明的同学肯定都猜到我这样赋值的作用了,但是还是要简述一下:
对于custom_attr1,我们在xml布局文件、style、defStyle和theme中均进行赋值,那最终得到的结果必然能证实谁的优先级最高.
对于custom_attr2,我们在style、defStyle和theme中进行赋值,通过得到的结果我们能知道谁的优先级第二高.
对于custom_attr3和custom_attr4的赋值情况我就不多解释了,我相信大家都懂得!!
同时,还需要大家注意的是,只要是layout布局文件中,无论是通过namespace直接为属性赋值,还是通过style为属性赋值,在构造函数获取时都对应了getTheme().obtainStyledAttributes方法中的第二个参数@StyleableRes int[] attrs

第三个参数defStyleAttr

这个参数的意思是:

原文: An attribute in the current theme that contains areference to a style resource that supplies defaults values for the TypedArray. Can be 0 to not look for defaults.
翻译: 这是当前Theme中的包含的一个指向style的引用.当我们没有给自定义View设置declare-styleable资源集合时,默认从这个集合里面查找布局文件中配置属性值.传入0表示不向该defStyleAttr中查找默认值.
为了测试该参数的作用和优先级,我们需要进行如下操作.

首先, 我们先声明一个refrence格式的属性, 用于表示style的引用.声明在之前的res/values/attrs_my_custom_view.xml文件里即可:

<attr name="MyCustomViewDefStyleAttr" format="reference"/>

然后,需要到AndroidManifest.xml中查看包含该自定义View的Activity所使用的主题是:

<activity>
 android:name="com.kevintan.eventbussample.MainActivity"
 android:theme="@style/AppTheme"
 android:label="@string/app_name" >
 <intent-filter>
  <action android:name="android.intent.action.MAIN" />
  <category android:name="android.intent.category.LAUNCHER" />
 </intent-filter>
</activity>

最后,在style.xml中的AppTheme主题下增加MyCustomViewDefStyleAttr的引用实现.

<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
 <!-- Customize your theme here. -->
 <item name="MyCustomViewDefStyleAttr">@style/MyCustomViewDefStyleAttrImpl</item>
</style>

<style name="MyCustomViewDefStyleAttrImpl">
 <item name="custom_attr1">attr1_defStyleAttr</item>
 <item name="custom_attr2">attr2_defStyleAttr</item>
 <item name="custom_attr3">attr3_defStyleAttr</item>
</style>

代码验证,记住如果要使用obtainStyledAttributes方法的第三个参数, 就需要在第三个构造函数中显示的调用getTheme()的obtainStyledAttributes方法.

public MyCustomView(Context context, AttributeSet attrs) {
 // 为defStyleAttr进行赋值
 this(context, attrs, R.attr.MyCustomViewDefStyleAttr);
}

public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);

 TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyCustomView, defStyleAttr, 0);

 String attr1 = ta.getString(R.styleable.MyCustomView_custom_attr1);
 String attr2 = ta.getString(R.styleable.MyCustomView_custom_attr2);
 String attr3 = ta.getString(R.styleable.MyCustomView_custom_attr3);
 String attr4 = ta.getString(R.styleable.MyCustomView_custom_attr4);

 Log.e("customview", "attr1=" + attr1);
 Log.e("customview", "attr2=" + attr2);
 Log.e("customview", "attr3=" + attr3);
 Log.e("customview", "attr4=" + attr4);
 ta.recycle();
}

代码中,有两点需要大家注意:

我在第二个构造参数中已经对defStyleAttr进行了赋值,第三个构造参数直接使用传入参数即可.
第三个构造参数中,我使用了getTheme()的obtainStyledAttributes方法来代替context的2个参数的obtainStyledAttributes构造方法.
示例结果:

05-28 17:19:56.542 23575-23575/? E/customview: attr1=attr1_xml
05-28 17:19:56.542 23575-23575/? E/customview: attr2=attr2_style
05-28 17:19:56.542 23575-23575/? E/customview: attr3=attr3_defStyleAttr
05-28 17:19:56.542 23575-23575/? E/customview: attr4=null

从结果可以看出,在主题中指定style引用的优先级是低于在xml中直接赋值和使用style字段的.

同时,我们还需要了解一点:
在Android系统中的控件,很多都在构造参数中使用了第三个参数,例如Button.这样做的好处是: 当我们切换不同的主题时,Button的样式也能随之进行改变.

第四个参数——通过defStyleRes为属性赋值

这个参数的意思是:

原文: A resource identifier of a style resource that supplies default values for the TypedArray, used only if defStyleAttr is 0 or can not be found in the theme. Can be 0 to not look for defaults.
翻译: 这是一个指向Style的资源ID,但是仅在defStyleAttr为0或者defStyleAttr不为0但Theme中没有为defStyleAttr属性赋值时起作用.
通过翻译,我们可以明确两点:

1.defStyleRes: 指向一个style引用.
2.defStyleRes的优先级低于defStyleAttr.
为了验证,我们先在theme.xml文件中定义一个style:

<style name="MyCustomViewDefStyleRes">
 <item name="custom_attr1">attr1_defStyleRes</item>
 <item name="custom_attr2">attr2_defStyleRes</item>
 <item name="custom_attr3">attr3_defStyleRes</item>
 <item name="custom_attr4">attr4_defStyleRes</item>
</style>

然后,我们在自定义View的第三个构造函数中的obtainStyledAttributes函数中进行赋值,具体方法如下:

public MyCustomView(Context context, AttributeSet attrs) {
 this(context, attrs, R.attr.MyCustomViewDefStyleAttr);
}

public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);

 // 为defStyleRes进行赋值
 TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyCustomView, defStyleAttr, R.style.MyCustomViewDefStyleRes);

 String attr1 = ta.getString(R.styleable.MyCustomView_custom_attr1);
 String attr2 = ta.getString(R.styleable.MyCustomView_custom_attr2);
 String attr3 = ta.getString(R.styleable.MyCustomView_custom_attr3);
 String attr4 = ta.getString(R.styleable.MyCustomView_custom_attr4);

 Log.e("customview", "attr1=" + attr1);
 Log.e("customview", "attr2=" + attr2);
 Log.e("customview", "attr3=" + attr3);
 Log.e("customview", "attr4=" + attr4);
 ta.recycle();
}

测试结果:

05-28 17:44:09.282 3137-3137/? E/customview: attr1=attr1_xml
05-28 17:44:09.282 3137-3137/? E/customview: attr2=attr2_style
05-28 17:44:09.282 3137-3137/? E/customview: attr3=attr3_defStyleAttr
05-28 17:44:09.282 3137-3137/? E/customview: attr4=null

重点:
如果大家认真的看实验结果,肯定会被上面的结果感到奇怪,明明指定了defStyleRes,为什么attr4的值还是null?
是因为之前讲过defStyleRes的使用优先级:只有当defStyleAttr为0或者当前Theme中没有给defStyleAttr属性赋值时才起作用.

所以,这里我们需要修改构造函数,将defStyleAttr设置为0.

public MyCustomView(Context context, AttributeSet attrs) {
 // 为了验证defStyleRes的作用,将defStyleAttr设置为0
 this(context, attrs, 0);
}

public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);

 TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyCustomView, defStyleAttr, R.style.MyCustomViewDefStyleRes);

 String attr1 = ta.getString(R.styleable.MyCustomView_custom_attr1);
 String attr2 = ta.getString(R.styleable.MyCustomView_custom_attr2);
 String attr3 = ta.getString(R.styleable.MyCustomView_custom_attr3);
 String attr4 = ta.getString(R.styleable.MyCustomView_custom_attr4);

 Log.e("customview", "attr1=" + attr1);
 Log.e("customview", "attr2=" + attr2);
 Log.e("customview", "attr3=" + attr3);
 Log.e("customview", "attr4=" + attr4);
 ta.recycle();
}

最终结果:

05-28 17:49:03.707 5772-5772/? E/customview: attr1=attr1_xml
05-28 17:49:03.707 5772-5772/? E/customview: attr2=attr2_style
05-28 17:49:03.707 5772-5772/? E/customview: attr3=attr3_defStyleRes
05-28 17:49:03.707 5772-5772/? E/customview: attr4=attr4_defStyleRes

后记

在文章结尾, 我们再次总结一下自定义属性的属性赋值优先级:

在布局xml中直接定义 > 在布局xml中通过style定义 > 自定义View所在的Activity的Theme中指定style引用 > 构造函数中defStyleRes指定的默认值.

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android自定义ViewGroup之实现FlowLayout流式布局

    整理总结自鸿洋的博客:http://blog.csdn.net/lmj623565791/article/details/38352503/  一.FlowLayout介绍  所谓FlowLayout,就是控件根据ViewGroup的宽,自动的往右添加,如果当前行剩余空间不足,则自动添加到下一行.有点像所有的控件都往左飘的感觉,第一行满了,往第二行飘~所以也叫流式布局.Android并没有提供流式布局,但是某些场合中,流式布局还是非常适合使用的,比如关键字标签,搜索热词列表等,比如下图: git

  • Android自定义View仿QQ健康界面

    最近一直在学习自定义View相关的知识,今天给大家带来的是QQ健康界面的实现.先看效果图: 可以设置数字颜色,字体颜色,运动步数,运动排名,运动平均步数,虚线下方的蓝色指示条的长度会随着平均步数改变而进行变化.整体效果还是和QQ运动健康界面很像的. 自定义View四部曲,一起来看看怎么实现的. 1.自定义view的属性: <?xml version="1.0" encoding="utf-8"?> <resources> //自定义属性名,定

  • Android自定义View仿微博运动积分动画效果

    自定义View一直是自己的短板,趁着公司项目不紧张的时候,多加强这方面的练习.这一系列文章主要记录自己在自定义View的学习过程中的心得与体会. 刷微博的时候,发现微博运动界面,运动积分的显示有一个很好看的动画效果.OK,就从这个开始我的自定义view之路! 看一下最后的效果图: 分数颜色,分数大小,外圆的颜色,圆弧的颜色都支持自己设置,整体还是和微博那个挺像的.一起看看自定义View是怎样一步一步实现的: 1.自定义view的属性: 在res/values/ 下建立一个attrs.xml ,

  • Android自定义View之继承TextView绘制背景

    本文实例为大家分享了TextView绘制背景的方法,供大家参考,具体内容如下 效果: 实现流程: 1.初始化:对画笔进行设置 mPaintIn = new Paint(); mPaintIn.setAntiAlias(true); mPaintIn.setDither(true); mPaintIn.setStyle(Paint.Style.FILL); mPaintIn.setColor(getResources().getColor(R.color.colorPrimary)); mPain

  • Android 自定义View的使用介绍

    在项目开发中,可能系统自带的一些widget不能满足我们的需求,这时就需要自定义View. 通过查看系统中的常用widget如Button,TextView,EditText,他们都继承自View,所以我们在继承自定义View的时候也自然的需要继承View.1.首先新建一个类LView继承自View 复制代码 代码如下: public class LView extends View { private Paint paint; public LView(Context context) {  

  • Android UI设计系列之自定义DrawView组件实现数字签名效果(5)

    最近项目中有个新的需求,用户在完交易需要进行输入支付密码付款的时候,要让用户签下自己的签名,提起到数字签名这个东西,感觉有点高大上,后来想想数字签名的原理也不是太复杂,主要实现原理就是利用了View的绘图原理,把用户在屏幕上的手指移动轨迹显示在屏幕上,接着把在屏幕上显示的轨迹View转换成一张图片,最后把图片保存到本地或者上传到服务器... 还是老规矩,首先看一下工程目录吧: public class DrawView extends View { /** * 签名画笔 */ private P

  • Android自定义View设定到FrameLayout布局中实现多组件显示的方法 分享

    如果想在自定义的View上面显示Button 等View组件需要完成如下任务 1.在自定义View的类中覆盖父类的构造(注意是2个参数的) 复制代码 代码如下: public class MyView2 extends View{ public MyView2(Context context,AttributeSet att) {super(context,att); } public void onDraw(Canvas c) { // 这里绘制你要的内容 } } 2.定义布局文件 复制代码

  • Android自定义View实现广告信息上下滚动效果

    先看看效果: 实现代码: public class ScrollBanner extends LinearLayout { private TextView mBannerTV1; private TextView mBannerTV2; private Handler handler; private boolean isShow; private int startY1, endY1, startY2, endY2; private Runnable runnable; private Li

  • android自定义进度条渐变色View的实例代码

    最近在公司,项目不是很忙了,偶尔看见一个兄台在CSDN求助,帮忙要一个自定义的渐变色进度条,我当时看了一下进度条,感觉挺漂亮的,就尝试的去自定义view实现了一个,废话不说,先上图吧! 这个自定义的view,完全脱离了android自带的ProgressView,并且没使用一张图片,这样就能更好的降低程序代码上的耦合性! 下面我贴出代码  ,大概讲解一下实现思路吧! 复制代码 代码如下: package com.spring.progressview; import android.conten

  • Android自定义View实现可以拖拽的GridView

    先看看效果图 主要思想: 1.监听触碰事件 2.用WindowManager添加拖曳的图片 3.用Collections.swap()交换List数据 自定义代码: public class DragGridVeiw extends GridView { private final int PRESS_TIME = 1000;//长按时间 private int mDownX;//触碰时的X坐标 private int mDownY;//触碰时的Y坐标 private int mMoveX;//

随机推荐