/* * Stan 2013/04/25 * ver 2.0.8 */ package com.stan.libs.imageview.scalable; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Matrix; import android.graphics.PointF; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import android.widget.ImageView; /* * @author: Stan */ public class StanScalableImageView extends ImageView { private Matrix matrix = new Matrix(); // mode can be in one of these 3 states private static final int NONE = 0; private static final int DRAG = 1; private static final int ZOOM = 2; private int mode = NONE; private static final int CLICK = 3; // Remember some things private PointF last = new PointF(); private PointF start = new PointF(); private float minScale = 1f;// default private float maxScale = 3f;// default private float minScaleTemp;// measure private float maxScaleTemp;// measure private float[] m; private float redundantXSpace, redundantYSpace; private float width, height; private float nowScale = 1f; private float origWidth, origHeight, imageWidth, imageHeight, redundantWidth, redundantHeight; private boolean fit = false; private ScaleGestureDetector mScaleDetector; public StanScalableImageView(Context context) { super(context); initStanScalableImageView(context); } public StanScalableImageView(Context context, AttributeSet attrs) { super(context, attrs); initStanScalableImageView(context); } public StanScalableImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initStanScalableImageView(context); } private void initStanScalableImageView(Context context) { super.setScaleType(ScaleType.CENTER); mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); matrix.setTranslate(1f, 1f); m = new float[9]; setImageMatrix(matrix); setScaleType(ScaleType.MATRIX); setOnTouchListener(new DragListener()); } @Override public void setImageBitmap(Bitmap bitmap) { super.setImageBitmap(bitmap); imageWidth = bitmap.getWidth(); imageHeight = bitmap.getHeight(); } @Override public void setImageDrawable(Drawable drawable) { super.setImageDrawable(drawable); imageWidth = drawable.getIntrinsicWidth(); imageHeight = drawable.getIntrinsicHeight(); } @Override public void setImageResource(int resourceID) { super.setImageResource(resourceID); imageWidth = getResources().getDrawable(resourceID).getIntrinsicWidth(); imageHeight = getResources().getDrawable(resourceID) .getIntrinsicHeight(); } public void setMaxZoom(float x) { this.maxScale = x; } public void setMinZoom(float x) { this.minScale = x; } public void setFit(boolean fit) { this.fit = fit; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); width = MeasureSpec.getSize(widthMeasureSpec); height = MeasureSpec.getSize(heightMeasureSpec); float scale; if (fit) { float scaleX = (float) width / (float) imageWidth; float scaleY = (float) height / (float) imageHeight; scale = Math.min(scaleX, scaleY); matrix.setScale(scale, scale); } else { scale = 1; } minScaleTemp = minScale * scale; maxScaleTemp = maxScale * scale; setImageMatrix(matrix); nowScale = scale; // Center the image redundantHeight = (scale * (float) imageHeight); redundantWidth = (scale * (float) imageWidth); redundantYSpace = (float) height - (scale * (float) imageHeight); redundantXSpace = (float) width - redundantWidth; redundantYSpace /= (float) 2; redundantXSpace /= (float) 2; matrix.getValues(m); float x = m[Matrix.MTRANS_X]; float y = m[Matrix.MTRANS_Y]; matrix.postTranslate(redundantXSpace - x, redundantYSpace - y); origWidth = width - 2 * redundantXSpace; origHeight = height - 2 * redundantYSpace; setImageMatrix(matrix); } private class DragListener implements OnTouchListener { @Override public boolean onTouch(View v, MotionEvent event) { mScaleDetector.onTouchEvent(event); matrix.getValues(m); float x = m[Matrix.MTRANS_X]; float y = m[Matrix.MTRANS_Y]; PointF curr = new PointF(event.getX(), event.getY()); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: last.set(event.getX(), event.getY()); start.set(last); if (nowScale * imageWidth > width * 1.05 || nowScale * imageHeight > height * 1.05) { mode = DRAG; } break; case MotionEvent.ACTION_MOVE: float deltaX = curr.x - last.x; float deltaY = curr.y - last.y; if (mode == DRAG) { float scaleWidth = Math.round(imageWidth * nowScale); float scaleHeight = Math.round(imageHeight * nowScale); // x if (scaleWidth < width) {// 不超過邊框時置中 deltaX = redundantXSpace - x - (imageWidth * nowScale - redundantWidth) / 2; } else { if (deltaX > 0 && x + deltaX > 0) {// 往右拉&&左邊拉過頭 deltaX = -x; } else if (deltaX < 0 && x + deltaX + scaleWidth < width) {// 往左拉&&右邊拉過頭 deltaX = width - x - scaleWidth; } } // y if (scaleHeight < height) {// 不超過邊框時置中 deltaY = redundantYSpace - y - (imageHeight * nowScale - redundantHeight) / 2; } else { if (deltaY > 0 && y + deltaY > 0) {// 往下拉&&上邊拉過頭 deltaY = -y; } else if (deltaY < 0 && y + deltaY + scaleHeight < height) {// 往上拉&&下邊拉過頭 deltaY = height - y - scaleHeight; } } matrix.postTranslate(deltaX, deltaY); last.set(curr.x, curr.y); } break; case MotionEvent.ACTION_UP: mode = NONE; int xDiff = (int) Math.abs(curr.x - start.x); int yDiff = (int) Math.abs(curr.y - start.y); if (xDiff < CLICK && yDiff < CLICK) performClick(); break; case MotionEvent.ACTION_POINTER_UP: mode = NONE; break; } setImageMatrix(matrix); invalidate(); return true; } } private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { @Override public boolean onScaleBegin(ScaleGestureDetector detector) { mode = ZOOM; last.set(detector.getFocusX(), detector.getFocusY()); start.set(last); return true; } @Override public boolean onScale(ScaleGestureDetector detector) { float mScaleFactor = detector.getScaleFactor(); float origScale = nowScale; nowScale *= mScaleFactor; if (nowScale > maxScaleTemp) { nowScale = maxScaleTemp; mScaleFactor = maxScaleTemp / origScale; } else if (nowScale < minScaleTemp) { nowScale = minScaleTemp; mScaleFactor = minScaleTemp / origScale; } if (origWidth * nowScale <= width || origHeight * nowScale <= height) { matrix.postScale(mScaleFactor, mScaleFactor, width / 2, height / 2); } else { matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY()); } matrix.getValues(m); float x = m[Matrix.MTRANS_X]; float y = m[Matrix.MTRANS_Y]; float scaleWidth = Math.round(imageWidth * nowScale); float scaleHeight = Math.round(imageHeight * nowScale); if (nowScale < origScale) {// 縮小時置中 if (x > 0 && x + scaleWidth > width) {// 只有左邊有空隙 matrix.postTranslate(-x, 0); } else if (x < 0 && x + scaleWidth < width) {// 只有右邊有空隙 matrix.postTranslate(width - x - scaleWidth, 0); } else if (x > 0 && x + scaleWidth < width) {// 兩邊都有空隙 matrix.postTranslate((width - scaleWidth) / 2 - x, 0); } if (y > 0 && y + scaleHeight > height) {// 只有上邊有空隙 matrix.postTranslate(0, -y); } else if (y < 0 && y + scaleHeight < height) {// 只有下邊有空隙 matrix.postTranslate(0, height - y - scaleHeight); } else if (y > 0 && y + scaleHeight < height) {// 兩邊都有空隙 matrix.postTranslate(0, (height - scaleHeight) / 2 - y); } } PointF curr = new PointF(detector.getFocusX(), detector.getFocusY()); float deltaX = curr.x - last.x; float deltaY = curr.y - last.y; last.set(curr.x, curr.y); // x if (scaleWidth < width) {// 不超過邊框時置中 deltaX = redundantXSpace - x - (imageWidth * nowScale - redundantWidth) / 2; return true;// 縮放這邊已經置中過了,跳調 } else { if (deltaX > 0 && x + deltaX > 0) {// 往右拉&&左邊拉過頭 deltaX = -x; } else if (deltaX < 0 && x + deltaX + scaleWidth < width) {// 往左拉&&右邊拉過頭 deltaX = width - x - scaleWidth; } } // y if (scaleHeight < height) {// 不超過邊框時置中 deltaY = redundantYSpace - y - (imageHeight * nowScale - redundantHeight) / 2; return true;// 縮放這邊已經置中過了,跳調 } else { if (deltaY > 0 && y + deltaY > 0) {// 往下拉&&上邊拉過頭 deltaY = -y; } else if (deltaY < 0 && y + deltaY + scaleHeight < height) {// 往上拉&&下邊拉過頭 deltaY = height - y - scaleHeight; } } matrix.postTranslate(deltaX, deltaY); return true; } } }
2013年4月24日 星期三
[Android]支援雙指手勢放大縮小移動的ImageView
2013年4月23日 星期二
[Android] Gallery一次只滑動一張
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public class ScrollOneImageAtATimeGallery extends Gallery { public ScrollOneImageAtATimeGallery(Context context) { super(context); } public ScrollOneImageAtATimeGallery(Context context, AttributeSet attrs) { super(context, attrs); } public ScrollOneImageAtATimeGallery(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } private boolean isScrollingLeft(MotionEvent e1, MotionEvent e2) { return e2.getX() > e1.getX(); } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { int kEvent; if (isScrollingLeft(e1, e2)) { // Check if scrolling left kEvent = KeyEvent.KEYCODE_DPAD_LEFT; } else { // Otherwise scrolling right kEvent = KeyEvent.KEYCODE_DPAD_RIGHT; } onKeyDown(kEvent, null); return true; } } |
2013年4月16日 星期二
[Android] 單行超出時無限自動跑馬TextView
code:
xml調用時須加上屬性:package com.stan.libs.textview; import android.content.Context; import android.util.AttributeSet; import android.widget.TextView; public class AlwaysMarqueeTextView extends TextView { public AlwaysMarqueeTextView(Context context) { super(context); } public AlwaysMarqueeTextView(Context context, AttributeSet attrs) { super(context, attrs); } public AlwaysMarqueeTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean isFocused() { return true; } }
android:ellipsize="marquee" android:marqueeRepeatLimit="marquee_forever" android:singleLine="true"
[Android] single line TextView自動調整text size填滿區域高度
某些情況下我們會想要使用字體Size剛好符合給定區域高度的單行TextView,
參照http://developer.android.com/guide/practices/screens_support.html#screen-independence使用dimen方式來調整卻存在許多問題。
Google後搜尋到http://stackoverflow.com/questions/5033012/auto-scale-textview-text-to-fit-within-bounds的方式,不過該方法是從初值自動縮小以適應高度,與填滿高不太一樣。
因此針對code修改後如下:
注意因為是跑for迴圈去try,size給的初值越接近則效能越好,所以dimen還是設一下巴!
參照http://developer.android.com/guide/practices/screens_support.html#screen-independence使用dimen方式來調整卻存在許多問題。
Google後搜尋到http://stackoverflow.com/questions/5033012/auto-scale-textview-text-to-fit-within-bounds的方式,不過該方法是從初值自動縮小以適應高度,與填滿高不太一樣。
因此針對code修改後如下:
注意因為是跑for迴圈去try,size給的初值越接近則效能越好,所以dimen還是設一下巴!
package com.stan.libs.textview; import android.content.Context; import android.text.Layout.Alignment; import android.text.StaticLayout; import android.text.TextPaint; import android.util.AttributeSet; import android.util.TypedValue; import android.widget.TextView; public class AutoResizeToFillHeightTextView extends TextView { // Minimum text size for this text view public static final float MAX_TEXT_SIZE = 999; public static final float MIN_TEXT_SIZE = 8; // Interface for resize notifications public interface OnTextResizeListener { public void onTextResize(TextView textView, float oldSize, float newSize); } // Registered resize listener private OnTextResizeListener mTextResizeListener; // Flag for text and/or size changes to force a resize private boolean mNeedsResize = false; // Text size that is set from code. This acts as a starting point for // resizing private float mTextSize; // Temporary upper bounds on the starting text size private float mMaxTextSize = MAX_TEXT_SIZE; // Lower bounds for text size private float mMinTextSize = MIN_TEXT_SIZE; // Text view line spacing multiplier private float mSpacingMult = 1.0f; // Text view additional line spacing private float mSpacingAdd = 0.0f; // Default constructor override public AutoResizeToFillHeightTextView(Context context) { this(context, null); } // Default constructor when inflating from XML file public AutoResizeToFillHeightTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } // Default constructor override public AutoResizeToFillHeightTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mTextSize = getTextSize(); } /** * If the text view size changed, set the force resize flag to true */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { if (w != oldw || h != oldh) { mNeedsResize = true; } } /** * Register listener to receive resize notifications * * @param listener */ public void setOnResizeListener(OnTextResizeListener listener) { mTextResizeListener = listener; } /** * Override the set text size to update our internal reference values */ @Override public void setTextSize(float size) { super.setTextSize(size); mTextSize = getTextSize(); } /** * Override the set text size to update our internal reference values */ @Override public void setTextSize(int unit, float size) { super.setTextSize(unit, size); mTextSize = getTextSize(); } /** * Override the set line spacing to update our internal reference values */ @Override public void setLineSpacing(float add, float mult) { super.setLineSpacing(add, mult); mSpacingMult = mult; mSpacingAdd = add; } /** * Set the upper text size limit and invalidate the view * * @param maxTextSize */ public void setMaxTextSize(float maxTextSize) { mMaxTextSize = maxTextSize; requestLayout(); invalidate(); } /** * Return upper text size limit * * @return */ public float getMaxTextSize() { return mMaxTextSize; } /** * Set the lower text size limit and invalidate the view * * @param minTextSize */ public void setMinTextSize(float minTextSize) { mMinTextSize = minTextSize; requestLayout(); invalidate(); } /** * Return lower text size limit * * @return */ public float getMinTextSize() { return mMinTextSize; } /** * Reset the text to the original size */ public void resetTextSize() { if (mTextSize > 0) { super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize); } } /** * Resize text after measuring */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (changed || mNeedsResize) { int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight(); int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop(); resizeText(widthLimit, heightLimit); } super.onLayout(changed, left, top, right, bottom); } /** * Resize the text size with default width and height */ public void resizeText() { int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop(); int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight(); resizeText(widthLimit, heightLimit); } /** * Resize the text size with specified width and height * * @param width * @param height */ public void resizeText(int width, int height) { CharSequence text = getText(); // Do not resize if the view does not have dimensions or there is no // text if (text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) { return; } // Get the text view's paint object TextPaint textPaint = getPaint(); // Store the current text size float oldTextSize = textPaint.getTextSize(); // If there is a max text size set, use the lesser of that and the // default text size float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize; // Get the required text height int textHeight = getTextHeight(text, textPaint, width, targetTextSize); // Until we either fit within our text view or we had reached our min // text size, incrementally try smaller sizes while (textHeight > height && targetTextSize > mMinTextSize) { targetTextSize = Math.max(targetTextSize - 2, mMinTextSize); textHeight = getTextHeight(text, textPaint, width, targetTextSize); } // Until we either fit within our text view or we had reached our max // text size, incrementally try bigger sizes while (textHeight < height && targetTextSize < mMaxTextSize) { targetTextSize = Math.min(targetTextSize + 2, mMaxTextSize); textHeight = getTextHeight(text, textPaint, width, targetTextSize); } // Some devices try to auto adjust line spacing, so force default line // spacing // and invalidate the layout as a side effect textPaint.setTextSize(targetTextSize); setLineSpacing(mSpacingAdd, mSpacingMult); // Notify the listener if registered if (mTextResizeListener != null) { mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize); } // Reset force resize flag mNeedsResize = false; } // Set the text size of the text paint object and use a static layout to // render text off screen before measuring private int getTextHeight(CharSequence source, TextPaint paint, int width, float textSize) { // Update the text paint object paint.setTextSize(textSize); // Measure using a static layout StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true); return layout.getHeight(); } }
訂閱:
文章 (Atom)