分享一个可拖拉缩放的Gallery应用示例

寒天 发布于 2014/06/13 17:55
阅读 1K+
收藏 3

部分代码来源于开源社区,整理之后做了一个gallery的demo,贴出来供大家参考

重写Gallery:

package com.example.viewflowdemo.view;

import com.example.viewflowdemo.MainActivity;

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Gallery;

/**
 * 自定义Gallery
 * 
 * @author 
 * @create 2014-3-24
 */
public class PicGallery extends Gallery {
	private GestureDetector gestureScanner;
	private MyImageView imageView;

	public PicGallery(Context context) {
		super(context);

	}

	public PicGallery(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	public void setDetector(GestureDetector dectector) {
		gestureScanner = dectector;
	}

	public PicGallery(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.setOnTouchListener(new OnTouchListener() {

			float baseValue;
			float originalScale;

			@Override
			public boolean onTouch(View v, MotionEvent event) {
				View view = PicGallery.this.getSelectedView();
				if (view instanceof MyImageView) {
					imageView = (MyImageView) view;

					if (event.getAction() == MotionEvent.ACTION_DOWN) {
						baseValue = 0;
						originalScale = imageView.getScale();
					}
					if (event.getAction() == MotionEvent.ACTION_MOVE) {
						if (event.getPointerCount() == 2) {
							float x = event.getX(0) - event.getX(1);
							float y = event.getY(0) - event.getY(1);
							float value = (float) Math.sqrt(x * x + y * y);// 计算两点的距离
							// System.out.println("value:" + value);
							if (baseValue == 0) {
								baseValue = value;
							} else {
								float scale = value / baseValue;// 当前两点间的距离除以手指落下时两点间的距离就是需要缩放的比例。
								// scale the image
								imageView.zoomTo(originalScale * scale, x + event.getX(1), y + event.getY(1));

							}
						}
					}
				}
				return false;
			}

		});
	}

	float v[] = new float[9];

	@Override
	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
		View view = PicGallery.this.getSelectedView();
		if (view instanceof MyImageView) {

			float xdistance = calXdistance(e1, e2);
			float min_distance = MainActivity.screenWidth / 4f;
			if (isScrollingLeft(e1, e2) && xdistance > min_distance) {
				kEvent = KeyEvent.KEYCODE_DPAD_LEFT;
			} else if (!isScrollingLeft(e1, e2) && xdistance > min_distance) {
				kEvent = KeyEvent.KEYCODE_DPAD_RIGHT;
			}

			imageView = (MyImageView) view;

			Matrix m = imageView.getImageMatrix();
			m.getValues(v);
			// 图片实时的上下左右坐标
			float left, right;
			// 图片的实时宽,高
			float width = imageView.getScale() * imageView.getImageWidth();
			float height = imageView.getScale() * imageView.getImageHeight();

			if ((int) width <= MainActivity.screenWidth && (int) height <= MainActivity.screenHeight)// 如果图片当前大小<屏幕大小,直接处理滑屏事件
			{
				super.onScroll(e1, e2, distanceX, distanceY);
			} else {
				left = v[Matrix.MTRANS_X];
				right = left + width;
				Rect r = new Rect();
				imageView.getGlobalVisibleRect(r);

				if (distanceX > 0)// 向左滑动
				{
					if (r.left > 0 || right < MainActivity.screenWidth) {// 判断当前ImageView是否显示完全
						super.onScroll(e1, e2, distanceX, distanceY);
					} else {
						imageView.postTranslate(-distanceX, -distanceY);
					}
				} else if (distanceX < 0)// 向右滑动
				{
					if (r.right < MainActivity.screenWidth || left > 0) {
						super.onScroll(e1, e2, distanceX, distanceY);
					} else {
						imageView.postTranslate(-distanceX, -distanceY);
					}
				}

			}

		} else {
			super.onScroll(e1, e2, distanceX, distanceY);
		}
		return false;
	}

	private boolean isScrollingLeft(MotionEvent e1, MotionEvent e2) {
		return e2.getX() > e1.getX();
	}

	private float calXdistance(MotionEvent e1, MotionEvent e2) {
		return Math.abs(e2.getX() - e1.getX());
	}

	@Override
	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
		return false;
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// Logger.d(DEBUG,"[PicGallery.onTouchEvent]"+"PicGallery.onTouchEvent");
		if (gestureScanner != null) {
			gestureScanner.onTouchEvent(event);
		}
		switch (event.getAction()) {
		case MotionEvent.ACTION_UP:
			// 判断边界是否越界
			View view = PicGallery.this.getSelectedView();
			if (view instanceof MyImageView) {

				if (kEvent != KEY_INVALID) { // 是否切换上一页或下一页
					onKeyDown(kEvent, null);
					kEvent = KEY_INVALID;
				}

				imageView = (MyImageView) view;
				float width = imageView.getScale() * imageView.getImageWidth();
				float height = imageView.getScale() * imageView.getImageHeight();
				// Logger.LOG("onTouchEvent", "width=" + width + ",height="
				// + height + ",screenWidth="
				// + PictureViewActivity.screenWidth + ",screenHeight="
				// + PictureViewActivity.screenHeight);
				if ((int) width <= MainActivity.screenWidth && (int) height <= MainActivity.screenHeight)// 如果图片当前大小<屏幕大小,判断边界
				{
					break;
				}
				float v[] = new float[9];
				Matrix m = imageView.getImageMatrix();
				m.getValues(v);
				float top = v[Matrix.MTRANS_Y];
				float bottom = top + height;
				if (top < 0 && bottom < MainActivity.screenHeight) {
					// imageView.postTranslateDur(-top, 200f);
					imageView.postTranslateDur(MainActivity.screenHeight - bottom, 200f);
				}
				if (top > 0 && bottom > MainActivity.screenHeight) {
					// imageView.postTranslateDur(PictureViewActivity.screenHeight
					// - bottom, 200f);
					imageView.postTranslateDur(-top, 200f);
				}

				float left = v[Matrix.MTRANS_X];
				float right = left + width;
				if (left < 0 && right < MainActivity.screenWidth) {
					// imageView.postTranslateXDur(-left, 200f);
					imageView.postTranslateXDur(MainActivity.screenWidth - right, 200f);
				}
				if (left > 0 && right > MainActivity.screenWidth) {
					// imageView.postTranslateXDur(PictureViewActivity.screenWidth
					// - right, 200f);
					imageView.postTranslateXDur(-left, 200f);
				}
			}
			break;
		}
		return super.onTouchEvent(event);
	}

	int kEvent = KEY_INVALID; // invalid
	public static final int KEY_INVALID = -1;
}



缩放ImageView:

package com.example.viewflowdemo.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.widget.ImageView;

/**
 * 可缩放imageview
 * 
 * @create 2014-3-24
 */
public class MyImageView extends ImageView {
	// This is the base transformation which is used to show the image
	// initially. The current computation for this shows the image in
	// it's entirety, letterboxing as needed. One could choose to
	// show the image as cropped instead.
	//
	// This matrix is recomputed when we go from the thumbnail image to
	// the full size image.
	protected Matrix mBaseMatrix = new Matrix();

	// This is the supplementary transformation which reflects what
	// the user has done in terms of zooming and panning.
	//
	// This matrix remains the same when we go from the thumbnail image
	// to the full size image.
	protected Matrix mSuppMatrix = new Matrix();

	// This is the final matrix which is computed as the concatentation
	// of the base matrix and the supplementary matrix.
	private final Matrix mDisplayMatrix = new Matrix();

	// Temporary buffer used for getting the values out of a matrix.
	private final float[] mMatrixValues = new float[9];

	// The current bitmap being displayed.
	protected Bitmap image = null;

	protected Handler mHandler = new Handler();

	int mThisWidth = -1, mThisHeight = -1;// 布局后的宽度和高度,由于是全屏显示,这两个值等于屏幕分辨率

	float mMaxZoom;// 最大缩放比例
	float mMinZoom;// 最小缩放比例

	private int imageWidth;// 图片的原始宽度
	private int imageHeight;// 图片的原始高度

	// float scaleRate;// 图片适应屏幕的缩放比例

	static final float SCALE_RATE = 1.25F;

	public MyImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

	public MyImageView(Context context) {
		super(context);
		init();
	}

	@Override
	public void setImageBitmap(Bitmap bitmap) {
		super.setImageBitmap(bitmap);
		image = bitmap;
		setImageHeight(bitmap.getHeight());
		setImageWidth(bitmap.getWidth());
		Drawable d = getDrawable();
		if (d != null) {
			d.setDither(true);
		}
	}

	// Center as much as possible in one or both axis. Centering is
	// defined as follows: if the image is scaled down below the
	// view's dimensions then center it (literally). If the image
	// is scaled larger than the view and is translated out of view
	// then translate it back into view (i.e. eliminate black bars).
	protected void center(boolean horizontal, boolean vertical) {
		if (image == null) {
			return;
		}

		Matrix m = getImageViewMatrix();

		RectF rect = new RectF(0, 0, image.getWidth(), image.getHeight());
		// RectF rect = new RectF(0, 0, imageWidth*getScale(),
		// imageHeight*getScale());

		m.mapRect(rect);

		float height = rect.height();
		float width = rect.width();

		float deltaX = 0, deltaY = 0;

		if (vertical) {
			int viewHeight = getHeight();
			if (height < viewHeight) {
				deltaY = (viewHeight - height) / 2 - rect.top;
			} else if (rect.top > 0) {
				deltaY = -rect.top;
			} else if (rect.bottom < viewHeight) {
				deltaY = getHeight() - rect.bottom;
			}
		}

		if (horizontal) {
			int viewWidth = getWidth();
			if (width < viewWidth) {
				deltaX = (viewWidth - width) / 2 - rect.left;
			} else if (rect.left > 0) {
				deltaX = -rect.left;
			} else if (rect.right < viewWidth) {
				deltaX = viewWidth - rect.right;
			}
		}

		postTranslate(deltaX, deltaY);
		setImageMatrix(getImageViewMatrix());
	}

	private void init() {
		setScaleType(ImageView.ScaleType.MATRIX);
	}

	// Setup the base matrix so that the image is centered and scaled properly.
	private void getProperBaseMatrix(Bitmap bitmap, Matrix matrix) {
		float viewWidth = getWidth();
		float viewHeight = getHeight();

		float w = bitmap.getWidth();
		float h = bitmap.getHeight();
		matrix.reset();

		// We limit up-scaling to 3x otherwise the result may look bad if it's
		// a small icon.
		float scale = Math.min(viewWidth / w, viewHeight / h);

		mMinZoom = scale;
		mMaxZoom = 2 * scale;

		matrix.postScale(scale, scale);

		matrix.postTranslate((viewWidth - w * scale) / 2F, (viewHeight - h * scale) / 2F);
	}

	protected float getValue(Matrix matrix, int whichValue) {
		matrix.getValues(mMatrixValues);
		return mMatrixValues[whichValue];
	}

	// Get the scale factor out of the matrix.
	protected float getScale(Matrix matrix) {
		return getValue(matrix, Matrix.MSCALE_X);
	}

	public float getScale() {
		return getScale(mSuppMatrix) * mMinZoom;
	}

	public float getScaleRate() {
		return getScale(mSuppMatrix);
	}

	public float getMiniZoom() {
		return mMinZoom;
	}

	public float getMaxZoom() {
		return mMaxZoom;
	}

	// Combine the base matrix and the supp matrix to make the final matrix.
	protected Matrix getImageViewMatrix() {
		// The final matrix is computed as the concatentation of the base matrix
		// and the supplementary matrix.
		mDisplayMatrix.set(mBaseMatrix);
		mDisplayMatrix.postConcat(mSuppMatrix);
		return mDisplayMatrix;
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
		super.onLayout(changed, left, top, right, bottom);
		mThisWidth = right - left;
		mThisHeight = bottom - top;
		if (image != null) {
			getProperBaseMatrix(image, mBaseMatrix);
			setImageMatrix(getImageViewMatrix());
		}
	}

	protected void zoomTo(float scale, float centerX, float centerY) {
		if (scale > mMaxZoom) {
			scale = mMaxZoom;
		} else if (scale < mMinZoom) {
			scale = mMinZoom;
		}

		float oldScale = getScale();
		float deltaScale = scale / oldScale;

		mSuppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
		setImageMatrix(getImageViewMatrix());
		center(true, true);
	}

	protected void zoomTo(final float scale, final float centerX, final float centerY, final float durationMs) {
		final float incrementPerMs = (scale - getScale()) / durationMs;
		final float oldScale = getScale();
		final long startTime = System.currentTimeMillis();

		mHandler.post(new Runnable() {
			public void run() {
				long now = System.currentTimeMillis();
				float currentMs = Math.min(durationMs, now - startTime);
				float target = oldScale + (incrementPerMs * currentMs);
				zoomTo(target, centerX, centerY);
				if (currentMs < durationMs) {
					mHandler.post(this);
				}
			}
		});
	}

	public void zoomTo(float scale) {
		float cx = getWidth() / 2F;
		float cy = getHeight() / 2F;

		zoomTo(scale, cx, cy);
	}

	protected void zoomToPoint(float scale, float pointX, float pointY) {
		float cx = getWidth() / 2F;
		float cy = getHeight() / 2F;

		panBy(cx - pointX, cy - pointY);
		zoomTo(scale, cx, cy);
	}

	protected void zoomIn() {
		zoomIn(SCALE_RATE);
	}

	protected void zoomOut() {
		zoomOut(SCALE_RATE);
	}

	protected void zoomIn(float rate) {
		if (getScale() >= mMaxZoom) {
			return; // Don't let the user zoom into the molecular level.
		}
		if (image == null) {
			return;
		}

		float cx = getWidth() / 2F;
		float cy = getHeight() / 2F;

		mSuppMatrix.postScale(rate, rate, cx, cy);
		setImageMatrix(getImageViewMatrix());
	}

	protected void zoomOut(float rate) {
		if (image == null) {
			return;
		}

		float cx = getWidth() / 2F;
		float cy = getHeight() / 2F;

		// Zoom out to at most 1x.
		Matrix tmp = new Matrix(mSuppMatrix);
		tmp.postScale(1F / rate, 1F / rate, cx, cy);

		if (getScale(tmp) < 1F) {
			mSuppMatrix.setScale(1F, 1F, cx, cy);
		} else {
			mSuppMatrix.postScale(1F / rate, 1F / rate, cx, cy);
		}
		setImageMatrix(getImageViewMatrix());
		center(true, true);
	}

	public void postTranslate(float dx, float dy) {
		mSuppMatrix.postTranslate(dx, dy);
		setImageMatrix(getImageViewMatrix());
	}

	float _dy = 0.0f;

	protected void postTranslateDur(final float dy, final float durationMs) {
		_dy = 0.0f;
		final float incrementPerMs = dy / durationMs;
		final long startTime = System.currentTimeMillis();
		mHandler.post(new Runnable() {
			public void run() {
				long now = System.currentTimeMillis();
				float currentMs = Math.min(durationMs, now - startTime);

				postTranslate(0, incrementPerMs * currentMs - _dy);
				_dy = incrementPerMs * currentMs;

				if (currentMs < durationMs) {
					mHandler.post(this);
				}
			}
		});
	}

	float _dx = 0.0f;

	protected void postTranslateXDur(final float dx, final float durationMs) {
		_dx = 0.0f;
		final float incrementPerMs = dx / durationMs;
		final long startTime = System.currentTimeMillis();
		mHandler.post(new Runnable() {
			public void run() {
				long now = System.currentTimeMillis();
				float currentMs = Math.min(durationMs, now - startTime);

				postTranslate(incrementPerMs * currentMs - _dx, 0);
				_dx = incrementPerMs * currentMs;

				if (currentMs < durationMs) {
					mHandler.post(this);
				}
			}
		});
	}

	protected void panBy(float dx, float dy) {
		postTranslate(dx, dy);
		setImageMatrix(getImageViewMatrix());
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
			event.startTracking();
			return true;
		}
		return super.onKeyDown(keyCode, event);
	}

	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) {
			if (getScale() > mMinZoom) {
				zoomTo(mMinZoom);
				return true;
			}
		}
		return super.onKeyUp(keyCode, event);
	}

	public int getImageWidth() {
		return imageWidth;
	}

	public void setImageWidth(int imageWidth) {
		this.imageWidth = imageWidth;
	}

	public int getImageHeight() {
		return imageHeight;
	}

	public void setImageHeight(int imageHeight) {
		this.imageHeight = imageHeight;
	}

}



Activity:

package com.example.viewflowdemo;

import java.util.ArrayList;

import android.app.Activity;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.TextView;

import com.example.viewflowdemo.view.MyImageView;
import com.example.viewflowdemo.view.PicGallery;

/**
 * 图片浏览
 * 
 * @author LiangZC
 * @create 2014-3-18
 */
public class ImageViewActivity extends Activity {

	private int position;
	private ArrayList<String> imgUrls;
	private PicGallery gallery;
	private TextView tv_name, tv_count;

	private String[] nameStrs;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		setContentView(R.layout.imageview_flow);
		initUI();
		position = getIntent().getIntExtra("position", 0);
		imgUrls = getIntent().getStringArrayListExtra("imgUrls");
		nameStrs = getIntent().getStringArrayExtra("names");
		gallery.setAdapter(new ZoomImageAdapter(this, imgUrls));
		gallery.setSelection(position);
		tv_count.setText(position + "/" + imgUrls.size());
		if (nameStrs != null && nameStrs.length > 0)
			tv_name.setText(nameStrs.length == imgUrls.size() ? nameStrs[position] : nameStrs[0]);
	}

	protected void initUI() {
		tv_name = (TextView) findViewById(R.id.tv_name);
		tv_count = (TextView) findViewById(R.id.tv_count);
		ViewGroup.LayoutParams lp = tv_name.getLayoutParams();
		lp.height = MainActivity.screenHeight / 5;
		tv_name.setLayoutParams(lp);
		lp = tv_count.getLayoutParams();
		lp.height = MainActivity.screenHeight / 5;
		tv_count.setLayoutParams(lp);
		tv_name.setMovementMethod(ScrollingMovementMethod.getInstance());
		tv_name.getBackground().setAlpha(40);
		tv_count.getBackground().setAlpha(40);
		gallery = (PicGallery) findViewById(R.id.pic_gallery);
		gallery.setVerticalFadingEdgeEnabled(false);// 取消竖直渐变边框
		gallery.setHorizontalFadingEdgeEnabled(false);// 取消水平渐变边框
		gallery.setDetector(new GestureDetector(this, new MySimpleGesture()));
		gallery.setOnItemClickListener(new OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
				// TODO Auto-generated method stub
				finish();
			}
		});
		gallery.setOnItemSelectedListener(new OnItemSelectedListener() {

			@Override
			public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
				// TODO Auto-generated method stub
				tv_count.setText((arg2 + 1) + "/" + imgUrls.size());
				if (nameStrs != null && nameStrs.length > 0)
					tv_name.setText(nameStrs.length == imgUrls.size() ? nameStrs[position] : nameStrs[0]);
			}

			@Override
			public void onNothingSelected(AdapterView<?> arg0) {
				// TODO Auto-generated method stub

			}
		});
	}

	private class MySimpleGesture extends SimpleOnGestureListener {
		// 按两下的第二下Touch down时触发
		public boolean onDoubleTap(MotionEvent e) {

			View view = gallery.getSelectedView();
			if (view instanceof MyImageView) {
				MyImageView imageView = (MyImageView) view;
				if (imageView.getScale() > imageView.getMiniZoom()) {
					imageView.zoomTo(imageView.getMiniZoom());
				} else {
					imageView.zoomTo(imageView.getMaxZoom());
				}

			} else {

			}
			return true;
		}

		@Override
		public boolean onSingleTapConfirmed(MotionEvent e) {
			return true;
		}
	}
}



Adapter(此处使用了开源框架xutils加载图片):

package com.example.viewflowdemo;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
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.ScaleType;

import com.example.viewflowdemo.view.MyImageView;
import com.lidroid.xutils.BitmapUtils;
import com.lidroid.xutils.bitmap.BitmapDisplayConfig;
import com.lidroid.xutils.bitmap.callback.BitmapLoadCallBack;
import com.lidroid.xutils.bitmap.callback.BitmapLoadFrom;

/**
 * 可缩放的图片展示
 * 
 * @author LiangZC
 * @create 2014-4-10
 */
public class ZoomImageAdapter extends BaseAdapter {
	private Context context;
	private ArrayList<String> imgUrls;
	private BitmapUtils bitmapUtils;
	private BitmapDisplayConfig bitmapDisplayConfig;

	public ZoomImageAdapter(Context context, ArrayList<String> imgUrls) {
		this.context = context;
		this.imgUrls = imgUrls;
		bitmapUtils = BitmapHelper.getInstance(context);
		bitmapDisplayConfig = new BitmapDisplayConfig();
		bitmapDisplayConfig.setLoadingDrawable(context.getResources().getDrawable(R.drawable.ic_launcher));
	}

	@Override
	public int getCount() {
		return imgUrls != null ? imgUrls.size() : 0;
	}

	@Override
	public Object getItem(int position) {
		return imgUrls.get(position);
	}

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

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		MyImageView view = null;
		if (convertView == null) {
			view = new MyImageView(context);
			Gallery.LayoutParams lp = new Gallery.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
			view.setLayoutParams(lp);
			view.setScaleType(ScaleType.FIT_CENTER);
//			view.setBackgroundColor(context.getResources().getColor(R.color.white));
			convertView = view;
		} else {
			view = (MyImageView) convertView;
		}
		bitmapUtils.display(view, imgUrls.get(position), bitmapDisplayConfig, new BitmapLoadCallBack<MyImageView>() {

			@Override
			public void onLoadCompleted(MyImageView arg0, String arg1, Bitmap arg2, BitmapDisplayConfig arg3, BitmapLoadFrom arg4) {
				// TODO Auto-generated method stub
				arg0.setScaleType(ScaleType.MATRIX);
				arg0.setImageBitmap(arg2);
			}

			@Override
			public void onLoadFailed(MyImageView arg0, String arg1, Drawable arg2) {
				// TODO Auto-generated method stub

			}
		});
		return convertView;
	}
}



加载中
0
Storm-Cai
Storm-Cai
虽然没试 但是精神值得肯定
返回顶部
顶部