Android实现CoverFlow效果控件的实例代码

最近研究了一下如何在Android上实现CoverFlow效果的控件,其实早在2010年,就有Neil Davies开发并开源出了这个控件,Neil大神的这篇博客地址。首先是阅读源码,弄明白核心思路后,自己重新写了一遍这个控件,并加入了详尽的注释以便日后查阅;而后在使用过程中,发现了有两点可以改进:

(1)初始图片位于中间,左边空了一半空间,比较难看,可以改为重复滚动地展示;

(2)由于图片一开始就需要加载出来,所以对内存开销较大,很容易OOM,需要对图片的内存空间进行压缩。

这个自定义控件包括4个部分,用于创建及提供图片对象的ImageAdapter,计算图片旋转角度等的自定义控件GalleryFlow,压缩采样率解析Bitmap的工具类BitmapScaleDownUtil,以及承载自定义控件的Gallery3DActivity。

首先是ImageAdapter,代码如下:

 package pym.test.gallery3d.widget;
 import pym.test.gallery3d.util.BitmapScaleDownUtil;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.Canvas;
 import android.graphics.LinearGradient;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.PaintFlagsDrawFilter;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Shader.TileMode;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
 import android.widget.Gallery;
 import android.widget.ImageView;
 /**
 * @author pengyiming
 * @date 2013-9-30
 * @function GalleryFlow适配器
 */
 public class ImageAdapter extends BaseAdapter
 {
 /* 数据段begin */
 private final String TAG = "ImageAdapter";
 private Context mContext;
 //图片数组
 private int[] mImageIds ;
 //图片控件数组
 private ImageView[] mImages;
 //图片控件LayoutParams
 private GalleryFlow.LayoutParams mImagesLayoutParams;
 /* 数据段end */
 /* 函数段begin */
 public ImageAdapter(Context context, int[] imageIds)
 {
 mContext = context;
 mImageIds = imageIds;
 mImages = new ImageView[mImageIds.length];
 mImagesLayoutParams = new GalleryFlow.LayoutParams(Gallery.LayoutParams.WRAP_CONTENT, Gallery.LayoutParams.WRAP_CONTENT);
 }
 /**
 * @function 根据指定宽高创建待绘制的Bitmap,并绘制到ImageView控件上
 * @param imageWidth
 * @param imageHeight
 * @return void
 */
 public void createImages(int imageWidth, int imageHeight)
 {
 // 原图与倒影的间距5px
 final int gapHeight = 5;
 int index = 0;
 for (int imageId : mImageIds)
 {
 /* step1 采样方式解析原图并生成倒影 */
 // 解析原图,生成原图Bitmap对象
 // Bitmap originalImage = BitmapFactory.decodeResource(mContext.getResources(), imageId);
 Bitmap originalImage = BitmapScaleDownUtil.decodeSampledBitmapFromResource(mContext.getResources(), imageId, imageWidth, imageHeight);
 int width = originalImage.getWidth();
 int height = originalImage.getHeight();
 // Y轴方向反向,实质就是X轴翻转
 Matrix matrix = new Matrix();
 matrix.setScale(1, -1);
 // 且仅取原图下半部分创建倒影Bitmap对象
 Bitmap reflectionImage = Bitmap.createBitmap(originalImage, 0, height / 2, width, height / 2, matrix, false);
  /* step2 绘制 */
 // 创建一个可包含原图+间距+倒影的新图Bitmap对象
 Bitmap bitmapWithReflection = Bitmap.createBitmap(width, (height + gapHeight + height / 2), Config.ARGB_8888);
 // 在新图Bitmap对象之上创建画布
 Canvas canvas = new Canvas(bitmapWithReflection);
 // 抗锯齿效果
 canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG));
 // 绘制原图
 canvas.drawBitmap(originalImage, 0, 0, null);
 // 绘制间距
 Paint gapPaint = new Paint();
 gapPaint.setColor(0xFFCCCCCC);
 canvas.drawRect(0, height, width, height + gapHeight, gapPaint);
 // 绘制倒影
 canvas.drawBitmap(reflectionImage, 0, height + gapHeight, null);
 /* step3 渲染 */
 // 创建一个线性渐变的渲染器用于渲染倒影
 Paint paint = new Paint();
 LinearGradient shader = new LinearGradient(0, height, 0, (height + gapHeight + height / 2), 0x70ffffff, 0x00ffffff, TileMode.CLAMP);
 // 设置画笔渲染器
 paint.setShader(shader);
 // 设置图片混合模式
 paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
 // 渲染倒影+间距
 canvas.drawRect(0, height, width, (height + gapHeight + height / 2), paint);
 /* step4 在ImageView控件上绘制 */
 ImageView imageView = new ImageView(mContext);
 imageView.setImageBitmap(bitmapWithReflection);
 imageView.setLayoutParams(mImagesLayoutParams);
 // 打log
 imageView.setTag(index);
  /* step5 释放heap */
 originalImage.recycle();
 reflectionImage.recycle();
 // bitmapWithReflection.recycle();
 mImages[index++] = imageView;
 }
 }
 @Override
 public int getCount()
 {
 return Integer.MAX_VALUE;
 }
 @Override
 public Object getItem(int position)
 {
 return mImages[position];
 }
 @Override
 public long getItemId(int position)

 {
 return position;
 }
 @Override
 public View getView(int position, View convertView, ViewGroup parent)

 {
 return mImages[position % mImages.length];
 }
 /* 函数段end */
 }

其次是GalleryFlow,代码如下:

package pym.test.gallery3d.widget;
 import android.content.Context;
 import android.graphics.Camera;
 import android.graphics.Matrix;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
 import android.view.animation.Transformation;
 import android.widget.Gallery;
 /**
 * @author pengyiming
 * @date 2013-9-30
 * @function 自定义控件
 */
 public class GalleryFlow extends Gallery
 {
 /* 数据段begin */
 private final String TAG = "GalleryFlow";
 // 边缘图片最大旋转角度
 private final float MAX_ROTATION_ANGLE = 75;
 // 中心图片最大前置距离
 private final float MAX_TRANSLATE_DISTANCE = -100;
 // GalleryFlow中心X坐标
 private int mGalleryFlowCenterX;
 // 3D变换Camera
 private Camera mCamera = new Camera();
 /* 数据段end */
 /* 函数段begin */
 public GalleryFlow(Context context, AttributeSet attrs)
 {
 super(context, attrs);
 // 开启,在滑动过程中,回调getChildStaticTransformation()
 this.setStaticTransformationsEnabled(true);
 }
 /**
 * @function 获取GalleryFlow中心X坐标
 * @return
 */
 private int getCenterXOfCoverflow()
 {
 return (getWidth() - getPaddingLeft() - getPaddingRight()) / 2 + getPaddingLeft();
 }
 /**
 * @function 获取GalleryFlow子view的中心X坐标
 * @param childView
 * @return
 */
 private int getCenterXOfView(View childView)
 {
 return childView.getLeft() + childView.getWidth() / 2;
 }
 /**
 * @note step1 系统调用measure()方法时,回调此方法;表明此时系统正在计算view的大小
 */
 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
 {
 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 mGalleryFlowCenterX = getCenterXOfCoverflow();
 Log.d(TAG, "onMeasure, mGalleryFlowCenterX = " + mGalleryFlowCenterX);
 }
 /**
 * @note step2 系统调用layout()方法时,回调此方法;表明此时系统正在给child view分配空间
 * @note 必定在onMeasure()之后回调,但与onSizeChanged()先后顺序不一定
 */
 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b)
 {
 super.onLayout(changed, l, t, r, b);
 mGalleryFlowCenterX = getCenterXOfCoverflow();
 Log.d(TAG, "onLayout, mGalleryFlowCenterX = " + mGalleryFlowCenterX);
 }
 /**
 * @note step2 系统调用measure()方法后,当需要绘制此view时,回调此方法;表明此时系统已计算完view的大小
 * @note 必定在onMeasure()之后回调,但与onSizeChanged()先后顺序不一定
 */
 @Override
 protected void onSizeChanged(int w, int h, int oldw, int oldh)
 {
 super.onSizeChanged(w, h, oldw, oldh);
 mGalleryFlowCenterX = getCenterXOfCoverflow();
 Log.d(TAG, "onSizeChanged, mGalleryFlowCenterX = " + mGalleryFlowCenterX);
 }
 @Override
 protected boolean getChildStaticTransformation(View childView, Transformation t)
 {
 // 计算旋转角度
 float rotationAngle = calculateRotationAngle(childView);
 // 计算前置距离

 float translateDistance = calculateTranslateDistance(childView);
 // 开始3D变换
 transformChildView(childView, t, rotationAngle, translateDistance);
 return true;
 }
 /**
 * @function 计算GalleryFlow子view的旋转角度
 * @note1 位于Gallery中心的图片不旋转
 * @note2 位于Gallery中心两侧的图片按照离中心点的距离旋转
 * @param childView
 * @return
 */
 private float calculateRotationAngle(View childView)
 {
 final int childCenterX = getCenterXOfView(childView);
 float rotationAngle = 0;

 rotationAngle = (mGalleryFlowCenterX - childCenterX) / (float) mGalleryFlowCenterX * MAX_ROTATION_ANGLE;
  if (rotationAngle > MAX_ROTATION_ANGLE)

 {
 rotationAngle = MAX_ROTATION_ANGLE;
 }
 else if (rotationAngle < -MAX_ROTATION_ANGLE)
 {
 rotationAngle = -MAX_ROTATION_ANGLE;
 }
 return rotationAngle;
 }
 /**
 * @function 计算GalleryFlow子view的前置距离
 * @note1 位于Gallery中心的图片前置
 * @note2 位于Gallery中心两侧的图片不前置
 * @param childView
 * @return
 */
 private float calculateTranslateDistance(View childView)
 {
 final int childCenterX = getCenterXOfView(childView);
 float translateDistance = 0;
 if (mGalleryFlowCenterX == childCenterX)
 {
 translateDistance = MAX_TRANSLATE_DISTANCE;
 }
 return translateDistance;
 }
 /**
 * @function 开始变换GalleryFlow子view
 * @param childView
 * @param t
 * @param rotationAngle
 * @param translateDistance
 */
 private void transformChildView(View childView, Transformation t, float rotationAngle, float translateDistance)

 {
 t.clear();
 t.setTransformationType(Transformation.TYPE_MATRIX); 

 final Matrix imageMatrix = t.getMatrix();
 final int imageWidth = childView.getWidth();
 final int imageHeight = childView.getHeight();
  mCamera.save();
 /* rotateY */
 // 在Y轴上旋转,位于中心的图片不旋转,中心两侧的图片竖向向里或向外翻转。
 mCamera.rotateY(rotationAngle);
 /* rotateY */
  /* translateZ */
 // 在Z轴上前置,位于中心的图片会有放大的效果
 mCamera.translate(0, 0, translateDistance);
 /* translateZ */
 // 开始变换(我的理解是:移动Camera,在2D视图上产生3D效果)
 mCamera.getMatrix(imageMatrix);
 imageMatrix.preTranslate(-imageWidth / 2, -imageHeight / 2);
 imageMatrix.postTranslate(imageWidth / 2, imageHeight / 2);
 mCamera.restore();
 }
 /* 函数段end */
 }

Bitmap解析用具BitmapScaleDownUtil,代码如下:

 package pym.test.gallery3d.util;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.view.Display;
 /**
 * @author pengyiming
 * @date 2013-9-30
 * @function Bitmap缩放处理工具类
 */
 public class BitmapScaleDownUtil
 {
 /* 数据段begin */
 private final String TAG = "BitmapScaleDownUtil";
 /* 数据段end */
 /* 函数段begin */
 /**
 * @function 获取屏幕大小
 * @param display
 * @return 屏幕宽高
 */
 public static int[] getScreenDimension(Display display)
 {
 int[] dimension = new int[2];
 dimension[0] = display.getWidth();
 dimension[1] = display.getHeight();
 return dimension;
 } 

 /**
 * @function 以取样方式加载Bitmap
 * @param res
 * @param resId
 * @param reqWidth
 * @param reqHeight
 * @return 取样后的Bitmap
 */
 public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight)
 {
 // step1,将inJustDecodeBounds置为true,以解析Bitmap真实尺寸
 final BitmapFactory.Options options = new BitmapFactory.Options();
 options.inJustDecodeBounds = true;
 BitmapFactory.decodeResource(res, resId, options);
 // step2,计算Bitmap取样比例
 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
 // step3,将inJustDecodeBounds置为false,以取样比列解析Bitmap
 options.inJustDecodeBounds = false;
 return BitmapFactory.decodeResource(res, resId, options);
 }
 /**
 * @function 计算Bitmap取样比例
 * @param options
 * @param reqWidth
 * @param reqHeight
 * @return 取样比例
 */
 private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
 {
 // 默认取样比例为1:1
 int inSampleSize = 1;
 // Bitmap原始尺寸
 final int width = options.outWidth;
 final int height = options.outHeight;
 // 取最大取样比例
 if (height > reqHeight || width > reqWidth)
 {
 final int widthRatio = Math.round((float) width / (float) reqWidth);
 final int heightRatio = Math.round((float) height / (float) reqHeight);
 // 取样比例为X:1,其中X>=1
 inSampleSize = Math.max(widthRatio, heightRatio);
 }
 return inSampleSize;
 }
 /* 函数段end */
 }

测试控件的Gallery3DActivity,代码如下:

 package pym.test.gallery3d.main;
 import pym.test.gallery3d.R;
 import pym.test.gallery3d.util.BitmapScaleDownUtil;
 import pym.test.gallery3d.widget.GalleryFlow;
 import pym.test.gallery3d.widget.ImageAdapter;
 import android.app.Activity;
 import android.content.Context;
 import android.os.Bundle;
 /**
 * @author pengyiming
 * @date 2013-9-30
 */
 public class Gallery3DActivity extends Activity
 {
 /* 数据段begin */
 private final String TAG = "Gallery3DActivity";
 private Context mContext;
 // 图片缩放倍率(相对屏幕尺寸的缩小倍率)
 public static final int SCALE_FACTOR = 8;
 // 图片间距(控制各图片之间的距离)
 private final int GALLERY_SPACING = -10;
 // 控件
 private GalleryFlow mGalleryFlow;
 /* 数据段end */
 /* 函数段begin */
 @Override
 protected void onCreate(Bundle savedInstanceState)
 {
 super.onCreate(savedInstanceState);
 mContext = getApplicationContext();
  setContentView(R.layout.gallery_3d_activity_layout);
 initGallery();
 }
 private void initGallery()
 {
 // 图片ID
 int[] images = {
  R.drawable.picture_1,
  R.drawable.picture_2,
 R.drawable.picture_3,
 R.drawable.picture_4,
 R.drawable.picture_5,
  R.drawable.picture_6,
  R.drawable.picture_7 };
 ImageAdapter adapter = new ImageAdapter(mContext, images);
 // 计算图片的宽高
 int[] dimension = BitmapScaleDownUtil.getScreenDimension(getWindowManager().getDefaultDisplay());
 int imageWidth = dimension[0] / SCALE_FACTOR;
 int imageHeight = dimension[1] / SCALE_FACTOR;
 // 初始化图片
 adapter.createImages(imageWidth, imageHeight);
 // 设置Adapter,显示位置位于控件中间,这样使得左右均可"无限"滑动
 mGalleryFlow = (GalleryFlow) findViewById(R.id.gallery_flow);
 mGalleryFlow.setSpacing(GALLERY_SPACING);
 mGalleryFlow.setAdapter(adapter);
 mGalleryFlow.setSelection(Integer.MAX_VALUE / 2);
 }
 /* 函数段end */
 }

see效果图~~~

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • android bitmap compress(图片压缩)代码

    android的照相功能随着手机硬件的发展,变得越来越强大,能够找出很高分辨率的图片.有些场景中,需要照相并且上传到服务,但是由于图片的大小太大,那么就上传就会很慢(在有些网络情况下),而且很耗流量,要想速度快,那么就需要减小图片的大小.减少图片的大小有两种方法,1. 照小图片: 2. 压缩大图片. 照相时获取小图片一般不太符合要求,因为,图片的清晰度会很差,但是这种情况有个好处就是应用速度会快些: 压缩图片,就是把大图片压缩小,降低图片的质量,在一定范围内,降低图片的大小,并且满足需求(图片仍

  • android图片压缩的3种方法实例

    android 图片压缩方法: 第一:质量压缩法: 复制代码 代码如下: private Bitmap compressImage(Bitmap image) { ByteArrayOutputStream baos = new ByteArrayOutputStream();        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中        int op

  • Android中3种图片压缩处理方法

    Android中图片的存在形式: 1:文件形式:二进制形式存在与硬盘中. 2:流的形式:二进制形式存在与内存中. 3:Bitmap的形式 三种形式的区别: 文件形式和流的形式:对图片体积大小并没有影响.也就是说,如果你手机SD卡上的图片通过流的形式读到内存中,在内存中的大小也是原图的大小. 注意:不是Bitmap的形式. Bitmap的形式:图片占用的内存会瞬间变大. 以下是代码的形式: /** * 图片压缩的方法总结 */ /* * 图片压缩的方法01:质量压缩方法 */ private Bi

  • android 将图片压缩到指定的大小的示例

    从网上收集后自己写的一个方法: 1.首先是一个根据分辨率压缩的类,首先对图片进行一次压缩 /** * 根据分辨率压缩图片比例 * * @param imgPath * @param w * @param h * @return */ private static Bitmap compressByResolution(String imgPath, int w, int h) { BitmapFactory.Options opts = new BitmapFactory.Options();

  • Android图片压缩上传之基础篇

    在android程序开发中我们经常见到需要上传图片的场景,在这里有个技术点,需要把图片压缩处理,然后再进行上传.这样可以减少流量的消耗,提高图片的上传速度等问题. 关于android如何压缩,网上的资料也是很多,但大多数都是代码片段,讲解压缩步骤,而没有一个实用的工具类库.那么如何将压缩算法封装成一个实用工具库呢?其中会遇到些什么问题,比如: 1.需要压缩的图片有多少 2.压缩后的图片是覆盖还是保存到另外的目录 3.如果是另存目录需要将原始图片删除吗 4.如果改变压缩后的图片的尺寸大小是按照原图

  • Android图片压缩方法并压缩到指定大小

    一.图片质量压缩 /** * 质量压缩方法 * @param image * @return */ public static Bitmap compressImage(Bitmap image) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); image.compress(Bitmap.CompressFormat.JPEG, 100, baos);// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中

  • Android实现CoverFlow效果控件的实例代码

    最近研究了一下如何在Android上实现CoverFlow效果的控件,其实早在2010年,就有Neil Davies开发并开源出了这个控件,Neil大神的这篇博客地址.首先是阅读源码,弄明白核心思路后,自己重新写了一遍这个控件,并加入了详尽的注释以便日后查阅:而后在使用过程中,发现了有两点可以改进: (1)初始图片位于中间,左边空了一半空间,比较难看,可以改为重复滚动地展示: (2)由于图片一开始就需要加载出来,所以对内存开销较大,很容易OOM,需要对图片的内存空间进行压缩. 这个自定义控件包括

  • JavaScript实现的可变动态数字键盘控件方式实例代码

    整理文档,搜刮出一个JavaScript实现的可变动态数字键盘控件方式实例代码,稍微整理精简一下做下分享. @sunRainAmazing JavaScript编写和实现的可变动态键盘密码输入控件,可以动态的生产数字键盘并显示,并且可以实现每次点击后密码键盘重新加载,可以手动刷新功能. 第一种方式,点击查看: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&qu

  • vue用递归组件写树形控件的实例代码

    最近在vue项目中遇到需要用树形控件的部分,比如导航目录是不确定的,所以必须要用树形结构,不管导航目录有几级,都可以自动显示出来,我一开始觉得element-ui有树形控件,不需要自己写,调用就可以了,后来才发现,调用完事之后,样式不可控,而且要加东西特别困难,无法满足项目需求,于是,一首<凉凉>送给自己,后来去翻vue官网,发现居然有递归组件,一开始我写了两个组件,互相调用,可以写出来,后来返现,如果项目要用到5棵树,我要写10个组件,而且样式控制起来超级恶心,于是我就各种查资料,原生的也试

  • Android实现字母导航控件的示例代码

    目录 自定义属性 Measure测量 坐标计算 绘制 Touch事件处理 数据组装 显示效果 今天分享一个以前实现的通讯录字母导航控件,下面自定义一个类似通讯录的字母导航 View,可以知道需要自定义的几个要素,如绘制字母指示器.绘制文字.触摸监听.坐标计算等,自定义完成之后能够达到的功能如下: 完成列表数据与字母之间的相互联动; 支持布局文件属性配置; 在布局文件中能够配置相关属性,如字母颜色.字母字体大小.字母指示器颜色等属性. 主要内容如下: 自定义属性 Measure测量 坐标计算 绘制

  • Android开发之TextView控件用法实例总结

    本文实例总结了Android开发之TextView控件用法.分享给大家供大家参考,具体如下: TextView控件可以向用户展现文本信息,我们可以设置该文本信息是否能编辑 1.TextView基本使用 在程序中创建TextView对象 在xml文件中布局使用 2.New Android Project-> Project name:TextView Build Target:Android 2.2 Application name:TextViewDemo Package name:com.b5

  • Android开发之TimePicker控件用法实例详解

    本文实例分析了Android开发之TimePicker控件用法.分享给大家供大家参考,具体如下: 新建项目: New Android Project-> Project name:HelloSpinner Build Target:Android 2.2 Application name:HelloSpinner Package name:com.b510 Create Activity:MainActivity Min SDK Version:9 Finish 运行效果: 如果: return

  • Android编程之Button控件用法实例分析

    本文实例讲述了Android编程之Button控件用法.分享给大家供大家参考,具体如下: 一.Button概述 android.widget.Button直接继承于android.wdiget.TextView. 直接子类有:CompoundButton. 间接子类有:CheckBox,RadioButton,Switch,ToggleButton. Button类表示一个"按钮"控件."按钮"控件可以被用户按下或者点击,来触发另一个操作. 二.Button的用法

  • Android下拉刷新控件PullToRefresh实例解析

    Android中很多时候都会用到上下拉刷新,这是一个很常用的功能,Android的v4包中也为我们提供了一种原生的下拉刷新控件--SwipeRefreshLayout,可以用它实现一个简洁的刷新效果,但今天我们的主角并不是它,而是一个很火的第三方的上下拉刷新控件--PullToRefresh.PullToRefresh包括PullToRefreshScrollView.PullToRefreshListView.PullToRefreshGridView等等很多为我们提供的控件,我们可以在xml

  • 轻松实现功能强大的Android刮奖效果控件(ScratchView)

    前言 我身边有一部分开发的小伙伴,存在着这样一种习惯.某一天,突然看到某一款 App 上有个很漂亮的自定义控件(动画)效果,就会绞尽脑子想办法去自己实现一发.当然,我自己也是属于这类型的骚年,看到某种效果就会手痒难耐琢磨着实现套路.个人觉得这是一种需求驱动进步的方法,当你绞尽脑子去实现自己想要的效果时,你就会发现你对 Android 自定义控件(动画)的知识体系认识越深,久而久之,自己也能轻松的造出各种控件(动画)效果.要是哪天,产品童鞋拿着个原型(或者对着某款 App )跟你讲:"XXXX,你

  • Android ExpandableListView展开列表控件使用实例

    你是否觉得手机QQ上的好友列表那个控件非常棒? 不是..... 那也没关系,学多一点知识对自己也有益无害. 那么我们就开始吧. 展开型列表控件, 原名ExpandableListView 是普通的列表控件进阶版, 可以自由的把列表进行收缩, 非常的方便兼好看. 首先看看我完成的截图, 虽然界面不漂亮, 但大家可以自己去修改界面. 该控件需要一个主界面XML 一个标题界面XML及一个列表内容界面XML 首先我们来看看 mian.xml 主界面 复制代码 代码如下: //该界面非常简单, 只要一个E

随机推荐