Android开发笔记(一百二十四)自定义相册

画廊Gallery

Gallery是一个早期的画廊控件,左右滑动手势可展示内嵌的图片列表,类似于一个平面的万花筒。虽然Android现在将Gallery标记为Deprecation(表示已废弃),建议开发者采用HorizontalScrollView或者ViewPager来代替,但是Gallery用做自定义相册来轮播图片其实是个挺好的选择,所以下面我们还是简单介绍它的用法,并结合其它控件加深对图像开发的理解。 Gallery的常用属性说明如下: spacing : 指定图片之间的间隔大小。 unselectedAlpha : 指定未选定图片的透明度。取值为0到1,0表示完全透明,1表示完全不透明。 Gallery的常用方法说明如下: setSpacing : 设置图片之间的间隔大小。 setUnselectedAlpha : 设置未选定图片的透明度。 setAdapter : 设置图像视图的适配器。 getSelectedItemId : 获取当前选中的图像id。0表示第一个图像。 setSelection : 设置当前选中第几个图像。 setOnItemClickListener : 设置单项的点击监听器。 现在我们结合Gallery与ImageView来观看画廊的相册效果,首先放置一个FrameLayout布局,里面放入一个Gallery控件与一个ImageView控件,其中ImageView控件要充满整个屏幕,Gallery控件可放在屏幕上方或下方;然后监听Gallery控件的单项点击事件,点击指定图片项时,便给ImageView控件填充该图片,也就是点小图看大图。 下面是Gallery与ImageView结合使用的效果截图:

下面是Gallery与ImageView结合使用的代码例子:

import com.example.exmcard.adapter.GalleryAdapter;
import com.example.exmcard.util.MetricsUtil;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Gallery;
import android.widget.ImageView;

@SuppressWarnings("deprecation")
public class GalleryActivity extends Activity implements OnItemClickListener {

	private ImageView iv_gallery;
	private Gallery gl_gallery;

	// 图片数组
	private int[] mImageRes = {
			R.drawable.scene1, R.drawable.scene2,
			R.drawable.scene3, R.drawable.scene4,
			R.drawable.scene5, R.drawable.scene6
			};
	private int dip_pad;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_gallery);
		dip_pad = MetricsUtil.dip2px(this, 20);

		iv_gallery = (ImageView) findViewById(R.id.iv_gallery);
		iv_gallery.setImageResource(mImageRes[0]);

		gl_gallery = (Gallery) findViewById(R.id.gl_gallery);
		gl_gallery.setPadding(dip_pad, dip_pad, dip_pad, dip_pad);
		gl_gallery.setSpacing(dip_pad);
		gl_gallery.setUnselectedAlpha(0.5f);
		gl_gallery.setAdapter(new GalleryAdapter(this, mImageRes));
		gl_gallery.setOnItemClickListener(this);
	}

	@Override
	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
		iv_gallery.setImageResource(mImageRes[position]);
	}

}

图像切换ImageSwitcher

可能大家已经发现,前面Gallery与ImageView在切换大图时比较生硬,前后两张图片闪一下就切过去,用户体验不够友好。那有没有办法让图片切换自然些呢,比如说通过渐变动画的方式?答案肯定是有的,就是把ImageView换成ImageSwitcher,通过ImageSwitcher控件来实现图片的切换动画。ImageSwitcher实质是个视图动画师ViewAnimator,用于处理前后图像的变换动画;与之对应的是,TextSwitcher用于处理前后文本的变换动画;另外ViewFlipper也是从ViewAnimator派生而来,则是用于处理两个页面视图的变换动画。有关ViewFlipper的详细介绍参见《Android开发笔记(二十一)横幅轮播页》。 ImageSwitcher的常用方法说明如下: setFactory : 设置一个视图工厂。该视图工厂对象从ViewFactory派生而来,内部需要重写makeView方法来返回视图工厂里的具体视图。对于ImageSwitcher来说,视图工厂应当返回的当然是ImageView对象了。 setImageResource : 设置当前图像的资源ID。该方法与下面的setImageDrawable和setImageURI为三选一操作,调用了其中一个方法,就无需调用另外两个方法。 setImageDrawable : 设置当前图像的Drawable对象。 setImageURI : 设置当前图像的URI地址。 setInAnimation : 设置当前图像的进入动画。 setOutAnimation : 设置前一个图像的退出动画。 按照ImageSwitcher的上述方法,我们便能实现前后两个图像的切换动画(如淡入淡出动画)。可是还没有实现左右滑动切换图片的功能,既然Gallery上的小图能够左右滑动,那么我们希望ImageSwitcher的大图也能够左右滑动,这时要借助于手势事件来实现滑动切换功能。首先定义一个GestureDetector对象;然后调用ImageSwitcher的setOnTouchListener方法设置触摸监听器OnTouchListener,在该监听器的onTouch方法中让GestureDetector对象接管事件处理;最后重写GestureDetector对象的手势监听器OnGestureListener,主要是在onFling方法中增加对左滑和右滑的处理逻辑判断。 下面是Gallery与ImageSwitcher结合使用的效果截图:

下面是Gallery与ImageSwitcher结合使用的代码例子:

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.Gallery;
import android.widget.ImageSwitcher;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.ViewSwitcher.ViewFactory;

import com.example.exmcard.adapter.GalleryAdapter;
import com.example.exmcard.util.MetricsUtil;

@SuppressWarnings("deprecation")
public class SwitcherActivity extends Activity implements 
		OnTouchListener, OnItemClickListener {

	private ImageSwitcher is_switcher;
	private Gallery gl_switcher;

	// 图片数组
	private int[] mImageRes = {
			R.drawable.scene1, R.drawable.scene2,
			R.drawable.scene3, R.drawable.scene4,
			R.drawable.scene5, R.drawable.scene6
			};
	private int dip_pad;

	private GestureDetector mGesture;
	private float mFlipGap = 20f;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_switcher);
		dip_pad = MetricsUtil.dip2px(this, 20);

		is_switcher = (ImageSwitcher) findViewById(R.id.is_switcher);
		is_switcher.setFactory(new ViewFactoryImpl());
		is_switcher.setImageResource(mImageRes[0]);
		mGesture = new GestureDetector(this, new GestureListener(this));
		is_switcher.setOnTouchListener(this);
		
		gl_switcher = (Gallery) findViewById(R.id.gl_switcher);
		gl_switcher.setPadding(dip_pad, dip_pad, dip_pad, dip_pad);
		gl_switcher.setSpacing(dip_pad);
		gl_switcher.setUnselectedAlpha(0.5f);
		gl_switcher.setAdapter(new GalleryAdapter(this, mImageRes));
		gl_switcher.setOnItemClickListener(this);
	}

	@Override
	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
		is_switcher.setInAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_in));
		is_switcher.setOutAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_out));
		is_switcher.setImageResource(mImageRes[position]);
	}

	public class ViewFactoryImpl implements ViewFactory {

		@Override
		public View makeView() {
			ImageView iv = new ImageView(SwitcherActivity.this);
			iv.setBackgroundColor(0xFFFFFFFF);
			iv.setScaleType(ScaleType.FIT_XY);
			iv.setLayoutParams(new ImageSwitcher.LayoutParams(
					LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
			return iv;
		}

	}

	@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		mGesture.onTouchEvent(event);
		return true;
	}

	private class GestureListener implements GestureDetector.OnGestureListener {
		private Context mContext;
		private GestureListener(Context context) {
			mContext = context;
		}

		@Override
		public final boolean onDown(MotionEvent event) {
			return true;
		}

		@Override
		public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
			if (e1.getX() - e2.getX() > mFlipGap) {
				is_switcher.setInAnimation(AnimationUtils.loadAnimation(mContext,
                        R.anim.push_left_in));
				is_switcher.setOutAnimation(AnimationUtils.loadAnimation(mContext,
                        R.anim.push_left_out));
				int next_pos = (int) (gl_switcher.getSelectedItemId()+1);
				if (next_pos >= mImageRes.length) {
					next_pos = 0;
				}
				is_switcher.setImageResource(mImageRes[next_pos]);
				gl_switcher.setSelection(next_pos);
				return true;
			}
			if (e1.getX() - e2.getX() < -mFlipGap) {
				is_switcher.setInAnimation(AnimationUtils.loadAnimation(mContext,
                        R.anim.push_right_in));
				is_switcher.setOutAnimation(AnimationUtils.loadAnimation(mContext,
                        R.anim.push_right_out));
				int pre_pos = (int) (gl_switcher.getSelectedItemId()-1);
				if (pre_pos < 0) {
					pre_pos = mImageRes.length-1;
				}
				is_switcher.setImageResource(mImageRes[pre_pos]);
				gl_switcher.setSelection(pre_pos);
				return true;
			}
			return false;
		}

		@Override
		public final void onLongPress(MotionEvent event) {
		}

		@Override
		public final boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
			return false;
		}

		@Override
		public final void onShowPress(MotionEvent event) {
		}

		@Override
		public boolean onSingleTapUp(MotionEvent event) {
			return false;
		}

	}

}

调色板Palette

Palette是Android在5.0中引入的调色板控件,它用于分析一个位图对象的整体色调,最后给出样品的色彩RGB值,这样开发者就可以根据具体图片动态设置整个页面的背景色,从而实现统一的页面风格。 Palette在android-support-v7-palette.jar中定义,同时需要最新的android-support-v4.jar支持。使用之前先在sdk的“sdk\extras\android\support\v7\palette\libs”目录中找到jar包并在自己的工程中引用,如果在运行过程中报错“Caused by: java.lang.NoClassDefFoundError: android.support.v4.graphics.ColorUtils”,则是因为Palette调用了v4包中新加的类ColorUtils,解决办法是把最新的android-support-v4.jar导入到你的工程。 Palette的常用方法主要是两个: from : 从指定的Bitmap对象生成一个调色板建造者对象Palette.Builder。然后调用该Builder对象的generate方法即开始色调分析,generate方法的参数是个PaletteAsyncListener监听器,监听器的onGenerated方法就是完成分析之后的回调处理。 getVibrantSwatch : 获得Palette对象的样品。该方法在onGenerated中调用,返回值是Palette.Swatch样品对象,调用该样品对象的getRgb方法即可获得样品的色彩值。 下面是Gallery与Palette结合使用的效果截图:

下面是Gallery与Palette结合使用的代码例子:

import com.example.exmcard.adapter.GalleryAdapter;
import com.example.exmcard.util.MetricsUtil;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v7.graphics.Palette;
import android.support.v7.graphics.Palette.PaletteAsyncListener;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Gallery;
import android.widget.ImageView;
import android.widget.AdapterView.OnItemClickListener;

@SuppressWarnings("deprecation")
public class PaletteActivity extends Activity implements 
		OnItemClickListener, PaletteAsyncListener {

	private ImageView iv_palette;
	private Gallery gl_palette;

	// 图片数组
	private int[] mImageRes = {
			R.drawable.scene1, R.drawable.scene2,
			R.drawable.scene3, R.drawable.scene4,
			R.drawable.scene5, R.drawable.scene6
			};
	private int dip_pad;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_palette);
		dip_pad = MetricsUtil.dip2px(this, 20);

		iv_palette = (ImageView) findViewById(R.id.iv_palette);
		showBackground(mImageRes[0]);
		
		gl_palette = (Gallery) findViewById(R.id.gl_palette);
		gl_palette.setPadding(dip_pad, dip_pad, dip_pad, dip_pad);
		gl_palette.setSpacing(dip_pad);
		gl_palette.setUnselectedAlpha(0.5f);
		gl_palette.setAdapter(new GalleryAdapter(this, mImageRes));
		gl_palette.setOnItemClickListener(this);
	}
	
	private void showBackground(int res_id) {
		Drawable drawable = getResources().getDrawable(res_id);
		Bitmap bitmap = ((BitmapDrawable)drawable).getBitmap();
		Palette.Builder builder = Palette.from(bitmap);
		builder.generate(this);
	}

	@Override
	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
		showBackground(mImageRes[position]);
	}

	@Override
	public void onGenerated(Palette palette) {
		Palette.Swatch swatch = palette.getVibrantSwatch();
		if (swatch != null) {
			iv_palette.setBackgroundColor(swatch.getRgb());
		}
	}

}

卡片视图CardView

CardView也是Android在5.0中新引入的卡片视图控件,顾名思义它拥有一个卡片式的圆角边框,边框外缘有一圈阴影,边框内缘有一圈空白。该控件的实现原理并不复杂,事实上早期便有许多人自己写了类似卡片效果的控件,只不过后来Android顺应民意推出了原生的卡片视图。 使用CardView之前,要把“sdk\extras\android\support\v7\cardview”导入为一个库工程,并引用到自己的工程中。如果在app运行的时候报错:“Caused by: java.lang.NoClassDefFoundError: android.support.v7.cardview.R$styleable”,这是因为CardView源码中引用了android.support.v7.cardview.R.styleable,而开发者自己的工程包名不是android.support.v7.cardview,所以就会找不到这个R$styleable。 解决步骤如下: 1、要引用整个android-support-v7-cardview工程,不能直接把android-support-v7-cardview.jar复制到自己工程的libs目录。 2、把project.properties里面的“target=android-19”改为“target=android-21”,注意库工程和自己的工程都要改。 3、库工程和自己的工程都Clean Project,然后再编译运行。 CardView的常用属性说明如下(因为引用的是库工程,所以CardView节点的属性要像自定义控件一样对待,即先在根节点定义一个命名空间app指向res-auto,然后再使用app:属性名称来定义属性值,不可直接使用android:属性名称): cardBackgroundColor : 指定卡片的背景颜色。 cardCornerRadius : 指定卡片的圆角半径。 cardElevation : 指定卡片内容距离阴影边缘的间隔。 contentPadding : 指定卡片边缘阴影的高程,即阴影的宽度。 CardView的常用方法说明如下: setCardBackgroundColor : 设置卡片的背景颜色。 setRadius : 设置卡片的圆角半径。 setContentPadding : 设置卡片内容距离阴影边缘的间隔。 setCardElevation : 设置卡片边缘阴影的高程,即阴影的宽度。 下面是Gallery与CardView结合使用的效果截图:

下面是Gallery与CardView结合使用的代码例子:

import com.example.exmcard.util.MetricsUtil;

import android.content.Context;
import android.support.v7.widget.CardView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.BaseAdapter;
import android.widget.Gallery;
import android.widget.ImageView;

@SuppressWarnings("deprecation")
public class CardAdapter extends BaseAdapter {

	private Context mContext;
	private int[] mImageRes;
	private int dip_pad;
	private int dip_radius;

	public CardAdapter(Context context, int[] imageRes) {
		mContext = context;
		mImageRes = imageRes;
		dip_pad = MetricsUtil.dip2px(mContext, 20);
		dip_radius = MetricsUtil.dip2px(mContext, 5);
	}

	@Override
	public int getCount() {
		return mImageRes.length;
	}

	@Override
	public Object getItem(int position) {
		return mImageRes[position];
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		CardView card = new CardView(mContext);
		//这里不能使用LinearLayout.LayoutParams。否则会报错“java.lang.ClassCastException: android.widget.LinearLayout$LayoutParams cannot be cast to android.widget.Gallery$LayoutParams”
		card.setLayoutParams(new Gallery.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
		card.setRadius(dip_radius);
		card.setContentPadding(dip_pad, dip_pad, dip_pad, dip_pad);
		card.setCardElevation(3f);
		//card.setCardBackgroundColor(Color.GREEN);

		ImageView iv = new ImageView(mContext);
		iv.setImageResource(mImageRes[position]);
		iv.setLayoutParams(new Gallery.LayoutParams(120, 160));
		iv.setScaleType(ImageView.ScaleType.FIT_XY);
		card.addView(iv);

		return card;
	}

}

下面是使用CardView的布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:padding="5dp" >

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center_horizontal|top"
        android:orientation="vertical" >

        <android.support.v7.widget.CardView
            android:id="@+id/cv_one"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal|top"
            app:cardCornerRadius="20dp" >

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:scaleType="fitCenter"
                android:src="@drawable/scene1" />
        </android.support.v7.widget.CardView>
    </LinearLayout>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center_horizontal|top"
        android:orientation="vertical" >

        <android.support.v7.widget.CardView
            android:id="@+id/cv_two"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal|top" >

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical" >

                <ImageView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:scaleType="fitCenter"
                    android:src="@drawable/scene2" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="center"
                    android:text="风景如画"
                    android:textColor="#000000"
                    android:textSize="17sp" />
            </LinearLayout>
        </android.support.v7.widget.CardView>
    </LinearLayout>

</LinearLayout>

点击下载本文用到的自定义相册的工程代码 点此查看Android开发笔记的完整目录

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券