package org.odk.collect.android.views;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.TextView;
/**
* @author ctsims
*
*/
public class ShrinkingTextView extends TextView {
boolean mExpanded = false;
boolean mInteractive = true;
int mAnimationDuration = 500;
ExpandAnimation mCurrentAnimation;
int mFullHeight = -1;
int mMaxHeight;
int mSetMaxHeight = -1;
int mAnimatingHeight= -1;
public ShrinkingTextView(Context context, int max) {
super(context);
this.setOnClickListener(new PanelToggler());
updateMaxHeight(max);
}
public void updateMaxHeight(int maxHeight) {
int contentHeightAdj = this.getPaddingBottom() + this.getPaddingTop();
float tH = this.getTextSize();
float lH = this.getLineHeight();
float stH = lH + contentHeightAdj;
mSetMaxHeight = maxHeight;
//Don't allow this control to be smaller than one line
if(maxHeight * 1f < stH) {
mMaxHeight = (int)stH;
} else {
//Otherwise, count up lines until this will spill over, then
//set the max height to be the bottom of the lowest line
int contentHeight = maxHeight - contentHeightAdj;
float countUp = tH;
while(countUp + lH < contentHeight) {
countUp = countUp + lH;
}
//We're now on the last full line. We either want to put our baseline at the bottom of this line,
//or the bottom of the next
if(countUp + tH < contentHeight) {
countUp = countUp + tH;
} else {
//countUp -= (lH - tH);
}
mMaxHeight = (int)(countUp + contentHeightAdj);
}
//TODO: Don't mess with this during animation?
//this.mMaxHeight = maxHeight;
resetDynamicLayout();
//TODO: request layout _on change_?
this.requestLayout();
}
/* (non-Javadoc)
* @see android.widget.TextView#draw(android.graphics.Canvas)
*/
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if(mInteractive && !mExpanded || isAnimating()) {
Rect r = new Rect();
this.getDrawingRect(r);
Paint p = new Paint();
float f = this.getTextSize();
int px = (int)(f / 2);
int bottom = r.bottom - this.getPaddingBottom();
int top = bottom - px;
int start = Color.argb(0, 255, 255, 255);
int end = Color.argb(220, 255, 255, 255);
Shader shader = new LinearGradient(r.left, top, r.left, bottom, start, end, TileMode.CLAMP);
p.setShader(shader);
r.set(r.left, top, r.right, bottom);
p.setStyle(Paint.Style.FILL);
canvas.drawRect(r, p);
}
}
/* (non-Javadoc)
* @see android.widget.TextView#setText(java.lang.CharSequence, android.widget.TextView.BufferType)
*/
@Override
public void setText(CharSequence text, BufferType type) {
super.setText(text, type);
this.resetDynamicLayout();
}
private void resetDynamicLayout() {
//don't manipulate the expanded flag.
mInteractive = true;
mFullHeight = -1;
if(mMaxHeight == -1) { mInteractive = false; mExpanded = false;}
}
private class PanelToggler implements OnClickListener {
public void onClick(View v) {
if(!mInteractive || isAnimating()) { return; }
if (mExpanded) {
mCurrentAnimation = new ExpandAnimation(mFullHeight, mMaxHeight);
} else {
mCurrentAnimation = new ExpandAnimation(mMaxHeight, mFullHeight);
}
mCurrentAnimation.setDuration(mAnimationDuration);
ShrinkingTextView.this.startAnimation(mCurrentAnimation);
mExpanded = !mExpanded;
}
}
/* (non-Javadoc)
* @see android.widget.TextView#onMeasure(int, int)
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(isAnimating()) {
this.setMeasuredDimension(this.getMeasuredWidth(), mAnimatingHeight);
}
if(mExpanded || !mInteractive || isAnimating()) {
return;
}
//We're in a collapsed state we think is interactive. First,
//measure the total height
mFullHeight = mFullHeight == -1 ? this.getMeasuredHeight() : mFullHeight;
//The full height here isn't greater than the max,
//no need for heroics
if(mFullHeight <= mSetMaxHeight) {
mInteractive = false;
return;
}
int measuredWidth = this.getMeasuredWidth();
this.setMeasuredDimension(measuredWidth, mMaxHeight);
}
private boolean isAnimating() {
if(mCurrentAnimation == null || mCurrentAnimation.hasEnded() || !mCurrentAnimation.hasStarted()){
return false;
}
return true;
}
private class ExpandAnimation extends Animation {
private final int mStartHeight;
private final int mDeltaHeight;
public ExpandAnimation(int startHeight, int endHeight) {
mStartHeight = startHeight;
mDeltaHeight = endHeight - startHeight;
}
/*
* (non-Javadoc)
* @see android.view.animation.Animation#applyTransformation(float, android.view.animation.Transformation)
*/
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
//ShrinkingTextView.this.setHeight((int) (mStartHeight + mDeltaHeight * interpolatedTime));
mAnimatingHeight = (int) (mStartHeight + mDeltaHeight * interpolatedTime);
ShrinkingTextView.this.requestLayout();
}
/*
* (non-Javadoc)
* @see android.view.animation.Animation#willChangeBounds()
*/
@Override
public boolean willChangeBounds() {
return true;
}
}
}