顯示具有 Android 中文 標籤的文章。 顯示所有文章
顯示具有 Android 中文 標籤的文章。 顯示所有文章

2013年4月24日 星期三

[Android]支援雙指手勢放大縮小移動的ImageView


/*
 * 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月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:
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;
 }
}
xml調用時須加上屬性:
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還是設一下巴!





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();
 }
}