这些效果都是基于一些前辈的基础上进行修改的,以满足自己想要的效果,在此对几位前辈表示感谢。
废话不多说了,先上效果图。
因为都是一些动画,图片可能无法看出效果,先说一下主要功能。
1.listview的动画反弹效果
2.点击图片全屏查看图片,可对图片进行双指放大缩小移动,点击屏幕图片消失,点击返回图片消失,弹出和消失时都有简单动画。
资源下载:http://download.csdn.net/detail/qiang_csd/7361673
listview的反弹通过scrollview实现,图片的查看通过popupwindow实现,需要注意的是scrollview嵌套listview时必须自定义listview重写onMeasure()方法,否则无法正确显示。
下面是主要的类:
DragImageView.java
package com.example.imitateWeChat;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.animation.ScaleAnimation;
import android.widget.ImageView;
/****
* 这里你要明白几个方法执行的流程: 首先ImageView是继承自View的子类.
* onLayout方法:是一个回调方法.该方法会在在View中的layout方法中执行,在执行layout方法前面会首先执行setFrame方法.
* layout方法:
* setFrame方法:判断我们的View是否发生变化,如果发生变化,那么将最新的l,t,r,b传递给View,然后刷新进行动态更新UI.
* 并且返回ture.没有变化返回false.
*
* invalidate方法:用于刷新当前控件,
*
*
*
*/
public class DragImageView extends ImageView {
private Activity mActivity;
private int screen_W, screen_H;// 可见屏幕的宽高度
private int bitmap_W, bitmap_H;// 当前图片宽高
private int MAX_W, MAX_H, MIN_W, MIN_H;// 极限值
private int current_Top, current_Right, current_Bottom, current_Left;// 当前图片上下左右坐标
private int start_Top = -1, start_Right = -1, start_Bottom = -1,
start_Left = -1;// 初始化默认位置.
private int start_x, start_y, current_x, current_y;// 触摸位置
private float beforeLenght, afterLenght;// 两触点距离
private float scale_temp;// 缩放比例
/**
* 模式 NONE:无 DRAG:拖拽. ZOOM:缩放
*
* @author zhangjia
*
*/
public enum MODE {
NONE, DRAG, ZOOM
};
public MODE mode = MODE.NONE;// 默认模式
private boolean isControl_V = false;// 垂直监控
private boolean isControl_H = false;// 水平监控
private ScaleAnimation scaleAnimation;// 缩放动画
public boolean isScaleAnim = false;// 缩放动画
private MyAsyncTask myAsyncTask;// 异步动画
private boolean isOnClick;
private OnClickCallback onClickCallBack;
/** 构造方法 **/
public DragImageView(Context context) {
super(context);
}
public void setmActivity(Activity mActivity) {
this.mActivity = mActivity;
}
/** 可见屏幕宽度 **/
public void setScreen_W(int screen_W) {
this.screen_W = screen_W;
}
/** 可见屏幕高度 **/
public void setScreen_H(int screen_H) {
this.screen_H = screen_H;
}
public OnClickCallback getOnClickCallBack() {
return onClickCallBack;
}
public void setOnClickCallBack(OnClickCallback onClickCallBack) {
this.onClickCallBack = onClickCallBack;
}
public DragImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/***
* 设置显示图片
*/
@Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
/** 获取图片宽高 **/
bitmap_W = bm.getWidth();
bitmap_H = bm.getHeight();
MAX_W = bitmap_W * 3;
MAX_H = bitmap_H * 3;
MIN_W = bitmap_W / 2;
MIN_H = bitmap_H / 2;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (start_Top == -1) {
start_Top = top;
start_Left = left;
start_Bottom = bottom;
start_Right = right;
}
}
/***
* touch 事件
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
/** 处理单点、多点触摸 **/
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
isOnClick = true;
onTouchDown(event);
break;
// 多点触摸
case MotionEvent.ACTION_POINTER_DOWN:
isOnClick = false;
onPointerDown(event);
break;
case MotionEvent.ACTION_MOVE:
isOnClick = false;
onTouchMove(event);
break;
case MotionEvent.ACTION_UP:
mode = MODE.NONE;
if (isOnClick) {
if (onClickCallBack != null) {
onClickCallBack.callback();
}
}
break;
// 多点松开
case MotionEvent.ACTION_POINTER_UP:
mode = MODE.NONE;
/** 执行缩放还原 **/
if (isScaleAnim) {
doScaleAnim();
}
break;
}
return true;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
onClickCallBack.callback();
}
return super.onKeyDown(keyCode, event);
}
/** 按下 **/
public void onTouchDown(MotionEvent event) {
mode = MODE.DRAG;
current_x = (int) event.getRawX();
current_y = (int) event.getRawY();
start_x = (int) event.getX();
start_y = current_y - this.getTop();
}
/** 两个手指 只能放大缩小 **/
public void onPointerDown(MotionEvent event) {
if (event.getPointerCount() == 2) {
mode = MODE.ZOOM;
beforeLenght = getDistance(event);// 获取两点的距离
}
}
/** 移动的处理 **/
public void onTouchMove(MotionEvent event) {
int left = 0, top = 0, right = 0, bottom = 0;
/** 处理拖动 **/
if (mode == MODE.DRAG) {
/** 在这里要进行判断处理,防止在drag时候越界 **/
/** 获取相应的l,t,r ,b **/
left = current_x - start_x;
right = current_x + this.getWidth() - start_x;
top = current_y - start_y;
bottom = current_y - start_y + this.getHeight();
/** 水平进行判断 **/
if (isControl_H) {
if (left >= 0) {
left = 0;
right = this.getWidth();
}
if (right <= screen_W) {
left = screen_W - this.getWidth();
right = screen_W;
}
} else {
left = this.getLeft();
right = this.getRight();
}
/** 垂直判断 **/
if (isControl_V) {
if (top >= 0) {
top = 0;
bottom = this.getHeight();
}
if (bottom <= screen_H) {
top = screen_H - this.getHeight();
bottom = screen_H;
}
} else {
top = this.getTop();
bottom = this.getBottom();
}
if (isControl_H || isControl_V)
this.setPosition(left, top, right, bottom);
current_x = (int) event.getRawX();
current_y = (int) event.getRawY();
}
/** 处理缩放 **/
else if (mode == MODE.ZOOM) {
afterLenght = getDistance(event);// 获取两点的距离
float gapLenght = afterLenght - beforeLenght;// 变化的长度
if (Math.abs(gapLenght) > 5f) {
scale_temp = afterLenght / beforeLenght;// 求的缩放的比例
this.setScale(scale_temp);
beforeLenght = afterLenght;
}
}
}
/** 获取两点的距离 **/
float getDistance(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return FloatMath.sqrt(x * x + y * y);
}
/** 实现处理拖动 **/
private void setPosition(int left, int top, int right, int bottom) {
this.layout(left, top, right, bottom);
}
/** 处理缩放 **/
void setScale(float scale) {
int disX = (int) (this.getWidth() * Math.abs(1 - scale)) / 4;// 获取缩放水平距离
int disY = (int) (this.getHeight() * Math.abs(1 - scale)) / 4;// 获取缩放垂直距离
// 放大
if (scale > 1 && this.getWidth() <= MAX_W) {
current_Left = this.getLeft() - disX;
current_Top = this.getTop() - disY;
current_Right = this.getRight() + disX;
current_Bottom = this.getBottom() + disY;
this.setFrame(current_Left, current_Top, current_Right,
current_Bottom);
/***
* 此时因为考虑到对称,所以只做一遍判断就可以了。
*/
if (current_Top <= 0 && current_Bottom >= screen_H) {
// Log.e("jj", "屏幕高度=" + this.getHeight());
isControl_V = true;// 开启垂直监控
} else {
isControl_V = false;
}
if (current_Left <= 0 && current_Right >= screen_W) {
isControl_H = true;// 开启水平监控
} else {
isControl_H = false;
}
}
// 缩小
else if (scale < 1 && this.getWidth() >= MIN_W) {
current_Left = this.getLeft() + disX;
current_Top = this.getTop() + disY;
current_Right = this.getRight() - disX;
current_Bottom = this.getBottom() - disY;
/***
* 在这里要进行缩放处理
*/
// 上边越界
if (isControl_V && current_Top > 0) {
current_Top = 0;
current_Bottom = this.getBottom() - 2 * disY;
if (current_Bottom < screen_H) {
current_Bottom = screen_H;
isControl_V = false;// 关闭垂直监听
}
}
// 下边越界
if (isControl_V && current_Bottom < screen_H) {
current_Bottom = screen_H;
current_Top = this.getTop() + 2 * disY;
if (current_Top > 0) {
current_Top = 0;
isControl_V = false;// 关闭垂直监听
}
}
// 左边越界
if (isControl_H && current_Left >= 0) {
current_Left = 0;
current_Right = this.getRight() - 2 * disX;
if (current_Right <= screen_W) {
current_Right = screen_W;
isControl_H = false;// 关闭
}
}
// 右边越界
if (isControl_H && current_Right <= screen_W) {
current_Right = screen_W;
current_Left = this.getLeft() + 2 * disX;
if (current_Left >= 0) {
current_Left = 0;
isControl_H = false;// 关闭
}
}
if (isControl_H || isControl_V) {
this.setFrame(current_Left, current_Top, current_Right,
current_Bottom);
} else {
this.setFrame(current_Left, current_Top, current_Right,
current_Bottom);
isScaleAnim = true;// 开启缩放动画
}
}
}
/***
* 缩放动画处理
*/
public void doScaleAnim() {
myAsyncTask = new MyAsyncTask(screen_W, this.getWidth(),
this.getHeight());
myAsyncTask.setLTRB(this.getLeft(), this.getTop(), this.getRight(),
this.getBottom());
myAsyncTask.execute();
isScaleAnim = false;// 关闭动画
}
/***
* 回缩动画執行
*/
class MyAsyncTask extends AsyncTask<Void, Integer, Void> {
private int screen_W, current_Width, current_Height;
private int left, top, right, bottom;
private float scale_WH;// 宽高的比例
/** 当前的位置属性 **/
public void setLTRB(int left, int top, int right, int bottom) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
private float STEP = 8f;// 步伐
private float step_H, step_V;// 水平步伐,垂直步伐
public MyAsyncTask(int screen_W, int current_Width, int current_Height) {
super();
this.screen_W = screen_W;
this.current_Width = current_Width;
this.current_Height = current_Height;
scale_WH = (float) current_Height / current_Width;
step_H = STEP;
step_V = scale_WH * STEP;
}
@Override
protected Void doInBackground(Void... params) {
while (current_Width <= bitmap_W) {
left -= step_H;
top -= step_V;
right += step_H;
bottom += step_V;
current_Width += 2 * step_H;
left = Math.max(left, start_Left);
top = Math.max(top, start_Top);
right = Math.min(right, start_Right);
bottom = Math.min(bottom, start_Bottom);
Log.e("jj", "top=" + top + ",bottom=" + bottom + ",left="
+ left + ",right=" + right);
onProgressUpdate(new Integer[] { left, top, right, bottom });
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
@Override
protected void onProgressUpdate(final Integer... values) {
super.onProgressUpdate(values);
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
setFrame(values[0], values[1], values[2], values[3]);
}
});
}
}
public interface OnClickCallback {
public void callback();
}
}
ReboundScrollView.java
package com.example.imitateWeChat;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.TranslateAnimation;
import android.widget.ScrollView;
/**
* 有弹性的ScrollView 实现下拉弹回和上拉弹回
*/
public class ReboundScrollView extends ScrollView {
private static final String TAG = "ElasticScrollView";
// 移动因子, 是一个百分比, 比如手指移动了100px, 那么View就只移动50px
// 目的是达到一个延迟的效果
private static final float MOVE_FACTOR = 0.2f;
// 松开手指后, 界面回到正常位置需要的动画时间
private static final int ANIM_TIME = 200;
// ScrollView的子View, 也是ScrollView的唯一一个子View
private View contentView;
// 手指按下时的Y值, 用于在移动时计算移动距离
// 如果按下时不能上拉和下拉, 会在手指移动时更新为当前手指的Y值
private float startY;
// 用于记录正常的布局位置
private Rect originalRect = new Rect();
// 手指按下时记录是否可以继续下拉
private boolean canPullDown = false;
// 手指按下时记录是否可以继续上拉
private boolean canPullUp = false;
// 在手指滑动的过程中记录是否移动了布局
private boolean isMoved = false;
public ReboundScrollView(Context context) {
super(context);
}
public ReboundScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
if (getChildCount() > 0) {
contentView = getChildAt(0);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (contentView == null)
return;
// ScrollView中的唯一子控件的位置信息, 这个位置信息在整个控件的生命周期中保持不变
originalRect.set(contentView.getLeft(), contentView.getTop(),
contentView.getRight(), contentView.getBottom());
}
/**
* 在该方法中获取ScrollView中的唯一子控件的位置信息 这个位置信息在整个控件的生命周期中保持不变
*/
/**
* 在触摸事件中, 处理上拉和下拉的逻辑
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (contentView == null) {
return super.dispatchTouchEvent(ev);
}
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// 判断是否可以上拉和下拉
canPullDown = isCanPullDown();
canPullUp = isCanPullUp();
// 记录按下时的Y值
startY = ev.getY();
break;
case MotionEvent.ACTION_UP:
if (!isMoved)
break; // 如果没有移动布局, 则跳过执行
// 开启动画
TranslateAnimation anim = new TranslateAnimation(0, 0,
contentView.getTop(), originalRect.top);
long time = Math.abs(contentView.getTop() - originalRect.top)
* ANIM_TIME / 50l;
anim.setDuration(time);
contentView.startAnimation(anim);
// 设置回到正常的布局位置
contentView.layout(originalRect.left, originalRect.top,
originalRect.right, originalRect.bottom);
// 将标志位设回false
canPullDown = false;
canPullUp = false;
isMoved = false;
break;
case MotionEvent.ACTION_MOVE:
// 在移动的过程中, 既没有滚动到可以上拉的程度, 也没有滚动到可以下拉的程度
if (!canPullDown && !canPullUp) {
startY = ev.getY();
canPullDown = isCanPullDown();
canPullUp = isCanPullUp();
break;
}
// 计算手指移动的距离
float nowY = ev.getY();
int deltaY = (int) (nowY - startY);
// 是否应该移动布局
boolean shouldMove = (canPullDown && deltaY > 0) // 可以下拉, 并且手指向下移动
|| (canPullUp && deltaY < 0) // 可以上拉, 并且手指向上移动
|| (canPullUp && canPullDown); // 既可以上拉也可以下拉(这种情况出现在ScrollView包裹的控件比ScrollView还小)
if (shouldMove) {
// 计算偏移量
int offset = (int) (deltaY * MOVE_FACTOR);
// 随着手指的移动而移动布局
contentView.layout(originalRect.left,
originalRect.top + offset, originalRect.right,
originalRect.bottom + offset);
isMoved = true; // 记录移动了布局
}
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
/**
* 判断是否滚动到顶部
*/
private boolean isCanPullDown() {
return getScrollY() == 0
|| contentView.getHeight() < getHeight() + getScrollY();
}
/**
* 判断是否滚动到底部
*/
private boolean isCanPullUp() {
return contentView.getHeight() <= getHeight() + getScrollY();
}
}
MyListAdapter.java
package com.example.imitateWeChat;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.TextView;
import com.example.imitateWeChat.DragImageView.MODE;
import com.example.imitateWeChat.DragImageView.OnClickCallback;
public class MyListAdapter extends BaseAdapter {
private Context mContext;
private List<ImageListDomain> lists;
private int resource;
private LayoutInflater inflater;
private int state_height;
public MyListAdapter(Context context, List<ImageListDomain> lists,
int resource) {
super();
this.mContext = context;
this.lists = lists;
this.resource = resource;
inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return lists.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return lists.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView = null;
TextView titleView = null;
TextView contentView = null;
if (convertView == null) {
convertView = inflater.inflate(resource, null);// 生成条目界面对象
titleView = (TextView) convertView.findViewById(R.id.titleView);
contentView = (TextView) convertView.findViewById(R.id.contentView);
imageView = (ImageView) convertView.findViewById(R.id.imageView);
ViewCache cache = new ViewCache();
cache.titleView = titleView;
cache.contentView = contentView;
cache.imageView = imageView;
convertView.setTag(cache);
} else {
ViewCache cache = (ViewCache) convertView.getTag();
titleView = cache.titleView;
contentView = cache.contentView;
imageView = cache.imageView;
}
ImageListDomain list = lists.get(position);
// 下面代码实现数据绑定
titleView.setText(list.getTitle());
contentView.setText(list.getContent());
imageView.setImageResource(list.getImage());
// 显示popupwindow
final PopupWindow popwindow = initPopwindow(BitmapFactory
.decodeResource(mContext.getResources(), list.getImage()));
imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
popwindow.showAtLocation(v, Gravity.NO_GRAVITY, 0, 0);
}
});
return convertView;
}
private PopupWindow initPopwindow(Bitmap bitmap) {
LayoutInflater mInflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View view = mInflater
.inflate(R.layout.popwindow_show_image, null);
final DragImageView drag = (DragImageView) view
.findViewById(R.id.div_main);
final PopupWindow popwindow = new PopupWindow(view,
Util.getScreenWidth(), Util.getScreenHeight());
popwindow.setFocusable(true);
popwindow.setBackgroundDrawable(new BitmapDrawable());// 不设置的话返回键无效
popwindow.setTouchable(true);
popwindow.setOutsideTouchable(true);
popwindow.setAnimationStyle(R.style.PopDownMenu);
view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
view.setOnTouchListener(new OnTouchListener() {
private boolean onlyClick;
@Override
public boolean onTouch(View v, MotionEvent event) {
/** 处理单点、多点触摸 **/
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
onlyClick = true;
drag.onTouchDown(event);
break;
// 多点触摸
case MotionEvent.ACTION_POINTER_DOWN:
onlyClick = false;
drag.onPointerDown(event);
break;
case MotionEvent.ACTION_MOVE:
onlyClick = false;
drag.onTouchMove(event);
break;
case MotionEvent.ACTION_UP:
drag.mode = MODE.NONE;
if (onlyClick) {
popwindow.dismiss();
}
break;
// 多点松开
case MotionEvent.ACTION_POINTER_UP:
drag.mode = MODE.NONE;
/** 执行缩放还原 **/
if (drag.isScaleAnim) {
drag.doScaleAnim();
}
break;
}
return true;// 返回false无法监听
}
});
drag.setImageBitmap(bitmap);
drag.setmActivity((Activity) mContext);
drag.setOnClickCallBack(new OnClickCallback() {
@Override
public void callback() {
popwindow.dismiss();
}
});
ViewTreeObserver viewTreeObserver = drag.getViewTreeObserver();
viewTreeObserver
.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (state_height == 0) {
// 获取状况栏高度
Rect frame = new Rect();
((Activity) mContext).getWindow().getDecorView()
.getWindowVisibleDisplayFrame(frame);
state_height = frame.top;
drag.setScreen_H(Util.getScreenHeight()
- state_height);
drag.setScreen_W(Util.getScreenWidth());
}
}
});
return popwindow;
}
private final class ViewCache {
public TextView titleView;
public TextView contentView;
public ImageView imageView;
}
}
MyListView.java
package com.example.imitateWeChat;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ListView;
public class MyListView extends ListView {
public MyListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public MyListView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 根据模式计算每个child的高度和宽度
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
代码中注释已经很全了,也就不多说了