Add SearchBar list animation

This commit is contained in:
seven332 committed 2015-04-21 18:58:52 +08:00
1 parent 708ba3a3e9
commit beec5d8776
18 files changed
+1523 -11

No files matched your search

@@ -0,0 +1,401 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import com.hippo.ehviewer.R;
/**
* A FrameLayout with a rounded corner background and shadow.
* <p>
* CardView uses <code>elevation</code> property on L for shadows and falls back to a custom shadow
* implementation on older platforms.
* <p>
* Due to expensive nature of rounded corner clipping, on platforms before L, CardView does not
* clip its children that intersect with rounded corners. Instead, it adds padding to avoid such
* intersection (See {@link #setPreventCornerOverlap(boolean)} to change this behavior).
* <p>
* Before L, CardView adds padding to its content and draws shadows to that area. This padding
* amount is equal to <code>maxCardElevation + (1 - cos45) * cornerRadius</code> on the sides and
* <code>maxCardElevation * 1.5 + (1 - cos45) * cornerRadius</code> on top and bottom.
* <p>
* Since padding is used to offset content for shadows, you cannot set padding on CardView.
* Instead,
* you can use content padding attributes in XML or {@link #setContentPadding(int, int, int, int)}
* in code to set the padding between the edges of the Card and children of CardView.
* <p>
* Note that, if you specify exact dimensions for the CardView, because of the shadows, its content
* area will be different between platforms before L and after L. By using api version specific
* resource values, you can avoid these changes. Alternatively, If you want CardView to add inner
* padding on platforms L and after as well, you can set {@link #setUseCompatPadding(boolean)} to
* <code>true</code>.
* <p>
* To change CardView's elevation in a backward compatible way, use
* {@link #setCardElevation(float)}. CardView will use elevation API on L and before L, it will
* change the shadow size. To avoid moving the View while shadow size is changing, shadow size is
* clamped by {@link #getMaxCardElevation()}. If you want to change elevation dynamically, you
* should call {@link #setMaxCardElevation(float)} when CardView is initialized.
*
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardCornerRadius
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardElevation
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardMaxElevation
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardUseCompatPadding
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardPreventCornerOverlap
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPadding
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingLeft
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingTop
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingRight
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingBottom
*/
public class CardView extends FrameLayout implements CardViewDelegate {
private static final CardViewImpl NO_ELEVATION_IMPL;
private static final CardViewImpl IMPL;
private CardViewImpl mImpl;
static {
if (Build.VERSION.SDK_INT >= 21) {
IMPL = new CardViewApi21();
NO_ELEVATION_IMPL = new CardViewJellybeanMr1();
} else if (Build.VERSION.SDK_INT >= 17) {
IMPL = new CardViewJellybeanMr1();
NO_ELEVATION_IMPL = IMPL;
} else {
IMPL = new CardViewEclairMr1();
NO_ELEVATION_IMPL = IMPL;
}
IMPL.initStatic();
NO_ELEVATION_IMPL.initStatic();
}
private boolean mCompatPadding;
private boolean mPreventCornerOverlap;
private final Rect mContentPadding = new Rect();
private final Rect mShadowBounds = new Rect();
public CardView(Context context) {
super(context);
initialize(context, null, 0);
}
public CardView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs, 0);
}
public CardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context, attrs, defStyleAttr);
}
@Override
public void setPadding(int left, int top, int right, int bottom) {
// NO OP
}
@Override
public void setPaddingRelative(int start, int top, int end, int bottom) {
// NO OP
}
/**
* Returns whether CardView will add inner padding on platforms L and after.
*
* @return True CardView adds inner padding on platforms L and after to have same dimensions
* with platforms before L.
*/
@Override
public boolean getUseCompatPadding() {
return mCompatPadding;
}
/**
* CardView adds additional padding to draw shadows on platforms before L.
* <p>
* This may cause Cards to have different sizes between L and before L. If you need to align
* CardView with other Views, you may need api version specific dimension resources to account
* for the changes.
* As an alternative, you can set this flag to <code>true</code> and CardView will add the same
* padding values on platforms L and after.
* <p>
* Since setting this flag to true adds unnecessary gaps in the UI, default value is
* <code>false</code>.
*
* @param useCompatPadding True if CardView should add padding for the shadows on platforms L
* and above.
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardUseCompatPadding
*/
public void setUseCompatPadding(boolean useCompatPadding) {
if (mCompatPadding == useCompatPadding) {
return;
}
mCompatPadding = useCompatPadding;
mImpl.onCompatPaddingChanged(this);
}
/**
* Sets the padding between the Card's edges and the children of CardView.
* <p>
* Depending on platform version or {@link #getUseCompatPadding()} settings, CardView may
* update these values before calling {@link android.view.View#setPadding(int, int, int, int)}.
*
* @param left The left padding in pixels
* @param top The top padding in pixels
* @param right The right padding in pixels
* @param bottom The bottom padding in pixels
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPadding
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingLeft
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingTop
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingRight
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingBottom
*/
public void setContentPadding(int left, int top, int right, int bottom) {
mContentPadding.set(left, top, right, bottom);
mImpl.updatePadding(this);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!(mImpl instanceof CardViewApi21)) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
switch (widthMode) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
final int minWidth = (int) Math.ceil(mImpl.getMinWidth(this));
widthMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(minWidth,
MeasureSpec.getSize(widthMeasureSpec)), widthMode);
break;
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
switch (heightMode) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
final int minHeight = (int) Math.ceil(mImpl.getMinHeight(this));
heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(minHeight,
MeasureSpec.getSize(heightMeasureSpec)), heightMode);
break;
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
private void initialize(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CardView, defStyleAttr, R.style.CardView_Light);
boolean forceNoElevation = a.getBoolean(R.styleable.CardView_forceNoElevation, false);
if (forceNoElevation) {
mImpl = NO_ELEVATION_IMPL;
} else {
mImpl = IMPL;
}
int backgroundColor = a.getColor(R.styleable.CardView_cardBackgroundColor, 0);
float radius = a.getDimension(R.styleable.CardView_cardCornerRadius, 0);
float elevation = a.getDimension(R.styleable.CardView_cardElevation, 0);
float maxElevation = a.getDimension(R.styleable.CardView_cardMaxElevation, 0);
mCompatPadding = a.getBoolean(R.styleable.CardView_cardUseCompatPadding, false);
mPreventCornerOverlap = a.getBoolean(R.styleable.CardView_cardPreventCornerOverlap, true);
int defaultPadding = a.getDimensionPixelSize(R.styleable.CardView_contentPadding, 0);
mContentPadding.left = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingLeft,
defaultPadding);
mContentPadding.top = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingTop,
defaultPadding);
mContentPadding.right = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingRight,
defaultPadding);
mContentPadding.bottom = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingBottom,
defaultPadding);
if (elevation > maxElevation) {
maxElevation = elevation;
}
a.recycle();
mImpl.initialize(this, context, backgroundColor, radius, elevation, maxElevation);
}
/**
* Updates the background color of the CardView
*
* @param color The new color to set for the card background
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor
*/
public void setCardBackgroundColor(int color) {
mImpl.setBackgroundColor(this, color);
}
/**
* Returns the inner padding after the Card's left edge
*
* @return the inner padding after the Card's left edge
*/
public int getContentPaddingLeft() {
return mContentPadding.left;
}
/**
* Returns the inner padding before the Card's right edge
*
* @return the inner padding before the Card's right edge
*/
public int getContentPaddingRight() {
return mContentPadding.right;
}
/**
* Returns the inner padding after the Card's top edge
*
* @return the inner padding after the Card's top edge
*/
public int getContentPaddingTop() {
return mContentPadding.top;
}
/**
* Returns the inner padding before the Card's bottom edge
*
* @return the inner padding before the Card's bottom edge
*/
public int getContentPaddingBottom() {
return mContentPadding.bottom;
}
/**
* Updates the corner radius of the CardView.
*
* @param radius The radius in pixels of the corners of the rectangle shape
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardCornerRadius
* @see #setRadius(float)
*/
public void setRadius(float radius) {
mImpl.setRadius(this, radius);
}
/**
* Returns the corner radius of the CardView.
*
* @return Corner radius of the CardView
* @see #getRadius()
*/
@Override
public float getRadius() {
return mImpl.getRadius(this);
}
/**
* Internal method used by CardView implementations to update the padding.
*
* @hide
*/
@Override
public void setShadowPadding(int left, int top, int right, int bottom) {
mShadowBounds.set(left, top, right, bottom);
super.setPadding(left + mContentPadding.left, top + mContentPadding.top,
right + mContentPadding.right, bottom + mContentPadding.bottom);
}
/**
* Updates the backward compatible elevation of the CardView.
*
* @param radius The backward compatible elevation in pixels.
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardElevation
* @see #getCardElevation()
* @see #setMaxCardElevation(float)
*/
public void setCardElevation(float radius) {
mImpl.setElevation(this, radius);
}
/**
* Returns the backward compatible elevation of the CardView.
*
* @return Elevation of the CardView
* @see #setCardElevation(float)
* @see #getMaxCardElevation()
*/
public float getCardElevation() {
return mImpl.getElevation(this);
}
/**
* Updates the backward compatible elevation of the CardView.
* <p>
* Calling this method has no effect if device OS version is L or newer and
* {@link #getUseCompatPadding()} is <code>false</code>.
*
* @param radius The backward compatible elevation in pixels.
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardElevation
* @see #setCardElevation(float)
* @see #getMaxCardElevation()
*/
public void setMaxCardElevation(float radius) {
mImpl.setMaxElevation(this, radius);
}
/**
* Returns the backward compatible elevation of the CardView.
*
* @return Elevation of the CardView
* @see #setMaxCardElevation(float)
* @see #getCardElevation()
*/
public float getMaxCardElevation() {
return mImpl.getMaxElevation(this);
}
/**
* Returns whether CardView should add extra padding to content to avoid overlaps with rounded
* corners on API versions 20 and below.
*
* @return True if CardView prevents overlaps with rounded corners on platforms before L.
* Default value is <code>true</code>.
*/
@Override
public boolean getPreventCornerOverlap() {
return mPreventCornerOverlap;
}
/**
* On API 20 and before, CardView does not clip the bounds of the Card for the rounded corners.
* Instead, it adds padding to content so that it won't overlap with the rounded corners.
* You can disable this behavior by setting this field to <code>false</code>.
* <p>
* Setting this value on API 21 and above does not have any effect unless you have enabled
* compatibility padding.
*
* @param preventCornerOverlap Whether CardView should add extra padding to content to avoid
* overlaps with the CardView corners.
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardPreventCornerOverlap
* @see #setUseCompatPadding(boolean)
*/
public void setPreventCornerOverlap(boolean preventCornerOverlap) {
if (preventCornerOverlap == mPreventCornerOverlap) {
return;
}
mPreventCornerOverlap = preventCornerOverlap;
mImpl.onPreventCornerOverlapChanged(this);
}
}
@@ -0,0 +1,111 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.view.View;
class CardViewApi21 implements CardViewImpl {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void initialize(CardViewDelegate cardView, Context context, int backgroundColor,
float radius, float elevation, float maxElevation) {
final RoundRectDrawable backgroundDrawable = new RoundRectDrawable(backgroundColor, radius);
cardView.setBackgroundDrawable(backgroundDrawable);
View view = (View) cardView;
view.setClipToOutline(true);
view.setElevation(elevation);
setMaxElevation(cardView, maxElevation);
}
@Override
public void setRadius(CardViewDelegate cardView, float radius) {
((RoundRectDrawable) (cardView.getBackground())).setRadius(radius);
}
@Override
public void initStatic() {
}
@Override
public void setMaxElevation(CardViewDelegate cardView, float maxElevation) {
((RoundRectDrawable) (cardView.getBackground())).setPadding(maxElevation,
cardView.getUseCompatPadding(), cardView.getPreventCornerOverlap());
updatePadding(cardView);
}
@Override
public float getMaxElevation(CardViewDelegate cardView) {
return ((RoundRectDrawable) (cardView.getBackground())).getPadding();
}
@Override
public float getMinWidth(CardViewDelegate cardView) {
return getRadius(cardView) * 2;
}
@Override
public float getMinHeight(CardViewDelegate cardView) {
return getRadius(cardView) * 2;
}
@Override
public float getRadius(CardViewDelegate cardView) {
return ((RoundRectDrawable) (cardView.getBackground())).getRadius();
}
@Override
public void setElevation(CardViewDelegate cardView, float elevation) {
((View) cardView).setElevation(elevation);
}
@Override
public float getElevation(CardViewDelegate cardView) {
return ((View) cardView).getElevation();
}
@Override
public void updatePadding(CardViewDelegate cardView) {
if (!cardView.getUseCompatPadding()) {
cardView.setShadowPadding(0, 0, 0, 0);
return;
}
float elevation = getMaxElevation(cardView);
final float radius = getRadius(cardView);
int hPadding = (int) Math.ceil(RoundRectDrawableWithShadow
.calculateHorizontalPadding(elevation, radius, cardView.getPreventCornerOverlap()));
int vPadding = (int) Math.ceil(RoundRectDrawableWithShadow
.calculateVerticalPadding(elevation, radius, cardView.getPreventCornerOverlap()));
cardView.setShadowPadding(hPadding, vPadding, hPadding, vPadding);
}
@Override
public void onCompatPaddingChanged(CardViewDelegate cardView) {
setMaxElevation(cardView, getMaxElevation(cardView));
}
@Override
public void onPreventCornerOverlapChanged(CardViewDelegate cardView) {
setMaxElevation(cardView, getMaxElevation(cardView));
}
@Override
public void setBackgroundColor(CardViewDelegate cardView, int color) {
((RoundRectDrawable) (cardView.getBackground())).setColor(color);
}
}
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.graphics.drawable.Drawable;
/**
* Interface provided by CardView to implementations.
* <p>
* Necessary to resolve circular dependency between base CardView and platform implementations.
*/
interface CardViewDelegate {
void setBackgroundDrawable(Drawable paramDrawable);
Drawable getBackground();
boolean getUseCompatPadding();
boolean getPreventCornerOverlap();
float getRadius();
void setShadowPadding(int left, int top, int right, int bottom);
}
@@ -0,0 +1,161 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.View;
class CardViewEclairMr1 implements CardViewImpl {
final RectF sCornerRect = new RectF();
@Override
public void initStatic() {
// Draws a round rect using 7 draw operations. This is faster than using
// canvas.drawRoundRect before JBMR1 because API 11-16 used alpha mask textures to draw
// shapes.
RoundRectDrawableWithShadow.sRoundRectHelper
= new RoundRectDrawableWithShadow.RoundRectHelper() {
@Override
public void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius,
Paint paint) {
final float twoRadius = cornerRadius * 2;
final float innerWidth = bounds.width() - twoRadius - 1;
final float innerHeight = bounds.height() - twoRadius - 1;
// increment it to account for half pixels.
if (cornerRadius >= 1f) {
cornerRadius += .5f;
sCornerRect.set(-cornerRadius, -cornerRadius, cornerRadius, cornerRadius);
int saved = canvas.save();
canvas.translate(bounds.left + cornerRadius, bounds.top + cornerRadius);
canvas.drawArc(sCornerRect, 180, 90, true, paint);
canvas.translate(innerWidth, 0);
canvas.rotate(90);
canvas.drawArc(sCornerRect, 180, 90, true, paint);
canvas.translate(innerHeight, 0);
canvas.rotate(90);
canvas.drawArc(sCornerRect, 180, 90, true, paint);
canvas.translate(innerWidth, 0);
canvas.rotate(90);
canvas.drawArc(sCornerRect, 180, 90, true, paint);
canvas.restoreToCount(saved);
//draw top and bottom pieces
canvas.drawRect(bounds.left + cornerRadius - 1f, bounds.top,
bounds.right - cornerRadius + 1f, bounds.top + cornerRadius,
paint);
canvas.drawRect(bounds.left + cornerRadius - 1f,
bounds.bottom - cornerRadius + 1f, bounds.right - cornerRadius + 1f,
bounds.bottom, paint);
}
//// center
canvas.drawRect(bounds.left, bounds.top + Math.max(0, cornerRadius - 1f),
bounds.right, bounds.bottom - cornerRadius + 1f, paint);
}
};
}
@Override
public void initialize(CardViewDelegate cardView, Context context, int backgroundColor,
float radius, float elevation, float maxElevation) {
RoundRectDrawableWithShadow background = createBackground(context, backgroundColor, radius,
elevation, maxElevation);
background.setAddPaddingForCorners(cardView.getPreventCornerOverlap());
cardView.setBackgroundDrawable(background);
updatePadding(cardView);
}
RoundRectDrawableWithShadow createBackground(Context context, int backgroundColor,
float radius, float elevation, float maxElevation) {
return new RoundRectDrawableWithShadow(context.getResources(), backgroundColor, radius,
elevation, maxElevation);
}
@Override
public void updatePadding(CardViewDelegate cardView) {
Rect shadowPadding = new Rect();
getShadowBackground(cardView).getMaxShadowAndCornerPadding(shadowPadding);
((View) cardView).setMinimumHeight((int) Math.ceil(getMinHeight(cardView)));
((View) cardView).setMinimumWidth((int) Math.ceil(getMinWidth(cardView)));
cardView.setShadowPadding(shadowPadding.left, shadowPadding.top,
shadowPadding.right, shadowPadding.bottom);
}
@Override
public void onCompatPaddingChanged(CardViewDelegate cardView) {
// NO OP
}
@Override
public void onPreventCornerOverlapChanged(CardViewDelegate cardView) {
getShadowBackground(cardView).setAddPaddingForCorners(cardView.getPreventCornerOverlap());
updatePadding(cardView);
}
@Override
public void setBackgroundColor(CardViewDelegate cardView, int color) {
getShadowBackground(cardView).setColor(color);
}
@Override
public void setRadius(CardViewDelegate cardView, float radius) {
getShadowBackground(cardView).setCornerRadius(radius);
updatePadding(cardView);
}
@Override
public float getRadius(CardViewDelegate cardView) {
return getShadowBackground(cardView).getCornerRadius();
}
@Override
public void setElevation(CardViewDelegate cardView, float elevation) {
getShadowBackground(cardView).setShadowSize(elevation);
}
@Override
public float getElevation(CardViewDelegate cardView) {
return getShadowBackground(cardView).getShadowSize();
}
@Override
public void setMaxElevation(CardViewDelegate cardView, float maxElevation) {
getShadowBackground(cardView).setMaxShadowSize(maxElevation);
updatePadding(cardView);
}
@Override
public float getMaxElevation(CardViewDelegate cardView) {
return getShadowBackground(cardView).getMaxShadowSize();
}
@Override
public float getMinWidth(CardViewDelegate cardView) {
return getShadowBackground(cardView).getMinWidth();
}
@Override
public float getMinHeight(CardViewDelegate cardView) {
return getShadowBackground(cardView).getMinHeight();
}
private RoundRectDrawableWithShadow getShadowBackground(CardViewDelegate cardView) {
return ((RoundRectDrawableWithShadow) cardView.getBackground());
}
}
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.content.Context;
/**
* Interface for platform specific CardView implementations.
*/
interface CardViewImpl {
void initialize(CardViewDelegate cardView, Context context, int backgroundColor, float radius, float elevation, float maxElevation);
void setRadius(CardViewDelegate cardView, float radius);
float getRadius(CardViewDelegate cardView);
void setElevation(CardViewDelegate cardView, float elevation);
float getElevation(CardViewDelegate cardView);
void initStatic();
void setMaxElevation(CardViewDelegate cardView, float maxElevation);
float getMaxElevation(CardViewDelegate cardView);
float getMinWidth(CardViewDelegate cardView);
float getMinHeight(CardViewDelegate cardView);
void updatePadding(CardViewDelegate cardView);
void onCompatPaddingChanged(CardViewDelegate cardView);
void onPreventCornerOverlapChanged(CardViewDelegate cardView);
void setBackgroundColor(CardViewDelegate cardView, int color);
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
class CardViewJellybeanMr1 extends CardViewEclairMr1 {
@Override
public void initStatic() {
RoundRectDrawableWithShadow.sRoundRectHelper
= new RoundRectDrawableWithShadow.RoundRectHelper() {
@Override
public void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius,
Paint paint) {
canvas.drawRoundRect(bounds, cornerRadius, cornerRadius, paint);
}
};
}
}
@@ -0,0 +1,132 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import static android.support.v7.widget.RoundRectDrawableWithShadow.calculateVerticalPadding;
import static android.support.v7.widget.RoundRectDrawableWithShadow.calculateHorizontalPadding;
/**
* Very simple drawable that draws a rounded rectangle background with arbitrary corners and also
* reports proper outline for L.
* <p>
* Simpler and uses less resources compared to GradientDrawable or ShapeDrawable.
*/
class RoundRectDrawable extends Drawable {
private float mRadius;
private final Paint mPaint;
private final RectF mBoundsF;
private final Rect mBoundsI;
private float mPadding;
private boolean mInsetForPadding = false;
private boolean mInsetForRadius = true;
public RoundRectDrawable(int backgroundColor, float radius) {
mRadius = radius;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mPaint.setColor(backgroundColor);
mBoundsF = new RectF();
mBoundsI = new Rect();
}
void setPadding(float padding, boolean insetForPadding, boolean insetForRadius) {
if (padding == mPadding && mInsetForPadding == insetForPadding &&
mInsetForRadius == insetForRadius) {
return;
}
mPadding = padding;
mInsetForPadding = insetForPadding;
mInsetForRadius = insetForRadius;
updateBounds(null);
invalidateSelf();
}
float getPadding() {
return mPadding;
}
@Override
public void draw(Canvas canvas) {
canvas.drawRoundRect(mBoundsF, mRadius, mRadius, mPaint);
}
private void updateBounds(Rect bounds) {
if (bounds == null) {
bounds = getBounds();
}
mBoundsF.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
mBoundsI.set(bounds);
if (mInsetForPadding) {
float vInset = calculateVerticalPadding(mPadding, mRadius, mInsetForRadius);
float hInset = calculateHorizontalPadding(mPadding, mRadius, mInsetForRadius);
mBoundsI.inset((int) Math.ceil(hInset), (int) Math.ceil(vInset));
// to make sure they have same bounds.
mBoundsF.set(mBoundsI);
}
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
updateBounds(bounds);
}
@Override
public void getOutline(Outline outline) {
outline.setRoundRect(mBoundsI, mRadius);
}
void setRadius(float radius) {
if (radius == mRadius) {
return;
}
mRadius = radius;
updateBounds(null);
invalidateSelf();
}
@Override
public void setAlpha(int alpha) {
// not supported because older versions do not support
}
@Override
public void setColorFilter(ColorFilter cf) {
// not supported because older versions do not support
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
public float getRadius() {
return mRadius;
}
public void setColor(int color) {
mPaint.setColor(color);
invalidateSelf();
}
}
@@ -0,0 +1,354 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.widget;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import com.hippo.ehviewer.R;
/**
* A rounded rectangle drawable which also includes a shadow around.
*/
class RoundRectDrawableWithShadow extends Drawable {
// used to calculate content padding
final static double COS_45 = Math.cos(Math.toRadians(45));
final static float SHADOW_MULTIPLIER = 1.5f;
final int mInsetShadow; // extra shadow to avoid gaps between card and shadow
/*
* This helper is set by CardView implementations.
* <p>
* Prior to API 17, canvas.drawRoundRect is expensive; which is why we need this interface
* to draw efficient rounded rectangles before 17.
* */
static RoundRectHelper sRoundRectHelper;
Paint mPaint;
Paint mCornerShadowPaint;
Paint mEdgeShadowPaint;
final RectF mCardBounds;
float mCornerRadius;
Path mCornerShadowPath;
// updated value with inset
float mMaxShadowSize;
// actual value set by developer
float mRawMaxShadowSize;
// multiplied value to account for shadow offset
float mShadowSize;
// actual value set by developer
float mRawShadowSize;
private boolean mDirty = true;
private final int mShadowStartColor;
private final int mShadowEndColor;
private boolean mAddPaddingForCorners = true;
/**
* If shadow size is set to a value above max shadow, we print a warning
*/
private boolean mPrintedShadowClipWarning = false;
RoundRectDrawableWithShadow(Resources resources, int backgroundColor, float radius,
float shadowSize, float maxShadowSize) {
mShadowStartColor = resources.getColor(R.color.cardview_shadow_start_color);
mShadowEndColor = resources.getColor(R.color.cardview_shadow_end_color);
mInsetShadow = resources.getDimensionPixelSize(R.dimen.cardview_compat_inset_shadow);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mPaint.setColor(backgroundColor);
mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mCornerShadowPaint.setStyle(Paint.Style.FILL);
mCornerRadius = (int) (radius + .5f);
mCardBounds = new RectF();
mEdgeShadowPaint = new Paint(mCornerShadowPaint);
mEdgeShadowPaint.setAntiAlias(false);
setShadowSize(shadowSize, maxShadowSize);
}
/**
* Casts the value to an even integer.
*/
private int toEven(float value) {
int i = (int) (value + .5f);
if (i % 2 == 1) {
return i - 1;
}
return i;
}
public void setAddPaddingForCorners(boolean addPaddingForCorners) {
mAddPaddingForCorners = addPaddingForCorners;
invalidateSelf();
}
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
mCornerShadowPaint.setAlpha(alpha);
mEdgeShadowPaint.setAlpha(alpha);
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mDirty = true;
}
void setShadowSize(float shadowSize, float maxShadowSize) {
if (shadowSize < 0 || maxShadowSize < 0) {
throw new IllegalArgumentException("invalid shadow size");
}
shadowSize = toEven(shadowSize);
maxShadowSize = toEven(maxShadowSize);
if (shadowSize > maxShadowSize) {
shadowSize = maxShadowSize;
if (!mPrintedShadowClipWarning) {
mPrintedShadowClipWarning = true;
}
}
if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {
return;
}
mRawShadowSize = shadowSize;
mRawMaxShadowSize = maxShadowSize;
mShadowSize = (int)(shadowSize * SHADOW_MULTIPLIER + mInsetShadow + .5f);
mMaxShadowSize = maxShadowSize + mInsetShadow;
mDirty = true;
invalidateSelf();
}
@Override
public boolean getPadding(Rect padding) {
int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius,
mAddPaddingForCorners));
int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius,
mAddPaddingForCorners));
padding.set(hOffset, vOffset, hOffset, vOffset);
return true;
}
static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
boolean addPaddingForCorners) {
if (addPaddingForCorners) {
return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
} else {
return maxShadowSize * SHADOW_MULTIPLIER;
}
}
static float calculateHorizontalPadding(float maxShadowSize, float cornerRadius,
boolean addPaddingForCorners) {
if (addPaddingForCorners) {
return (float) (maxShadowSize + (1 - COS_45) * cornerRadius);
} else {
return maxShadowSize;
}
}
@Override
public void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
mCornerShadowPaint.setColorFilter(cf);
mEdgeShadowPaint.setColorFilter(cf);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
void setCornerRadius(float radius) {
radius = (int) (radius + .5f);
if (mCornerRadius == radius) {
return;
}
mCornerRadius = radius;
mDirty = true;
invalidateSelf();
}
@Override
public void draw(Canvas canvas) {
if (mDirty) {
buildComponents(getBounds());
mDirty = false;
}
canvas.translate(0, mRawShadowSize / 2);
drawShadow(canvas);
canvas.translate(0, -mRawShadowSize / 2);
sRoundRectHelper.drawRoundRect(canvas, mCardBounds, mCornerRadius, mPaint);
}
private void drawShadow(Canvas canvas) {
final float edgeShadowTop = -mCornerRadius - mShadowSize;
final float inset = mCornerRadius + mInsetShadow + mRawShadowSize / 2;
final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0;
final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0;
// LT
int saved = canvas.save();
canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset);
canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
if (drawHorizontalEdges) {
canvas.drawRect(0, edgeShadowTop,
mCardBounds.width() - 2 * inset, -mCornerRadius,
mEdgeShadowPaint);
}
canvas.restoreToCount(saved);
// RB
saved = canvas.save();
canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset);
canvas.rotate(180f);
canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
if (drawHorizontalEdges) {
canvas.drawRect(0, edgeShadowTop,
mCardBounds.width() - 2 * inset, -mCornerRadius + mShadowSize,
mEdgeShadowPaint);
}
canvas.restoreToCount(saved);
// LB
saved = canvas.save();
canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset);
canvas.rotate(270f);
canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
if (drawVerticalEdges) {
canvas.drawRect(0, edgeShadowTop,
mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
}
canvas.restoreToCount(saved);
// RT
saved = canvas.save();
canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset);
canvas.rotate(90f);
canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
if (drawVerticalEdges) {
canvas.drawRect(0, edgeShadowTop,
mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
}
canvas.restoreToCount(saved);
}
private void buildShadowCorners() {
RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
RectF outerBounds = new RectF(innerBounds);
outerBounds.inset(-mShadowSize, -mShadowSize);
if (mCornerShadowPath == null) {
mCornerShadowPath = new Path();
} else {
mCornerShadowPath.reset();
}
mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
mCornerShadowPath.moveTo(-mCornerRadius, 0);
mCornerShadowPath.rLineTo(-mShadowSize, 0);
// outer arc
mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
// inner arc
mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
mCornerShadowPath.close();
float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
new float[]{0f, startRatio, 1f}
, Shader.TileMode.CLAMP));
// we offset the content shadowSize/2 pixels up to make it more realistic.
// this is why edge shadow shader has some extra space
// When drawing bottom edge shadow, we use that extra space.
mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0,
-mCornerRadius - mShadowSize,
new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
mEdgeShadowPaint.setAntiAlias(false);
}
private void buildComponents(Rect bounds) {
// Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
// We could have different top-bottom offsets to avoid extra gap above but in that case
// center aligning Views inside the CardView would be problematic.
final float verticalOffset = mRawMaxShadowSize * SHADOW_MULTIPLIER;
mCardBounds.set(bounds.left + mRawMaxShadowSize, bounds.top + verticalOffset,
bounds.right - mRawMaxShadowSize, bounds.bottom - verticalOffset);
buildShadowCorners();
}
float getCornerRadius() {
return mCornerRadius;
}
void getMaxShadowAndCornerPadding(Rect into) {
getPadding(into);
}
void setShadowSize(float size) {
setShadowSize(size, mRawMaxShadowSize);
}
void setMaxShadowSize(float size) {
setShadowSize(mRawShadowSize, size);
}
float getShadowSize() {
return mRawShadowSize;
}
float getMaxShadowSize() {
return mRawMaxShadowSize;
}
float getMinWidth() {
final float content = 2 *
Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2);
return content + (mRawMaxShadowSize + mInsetShadow) * 2;
}
float getMinHeight() {
final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow
+ mRawMaxShadowSize * SHADOW_MULTIPLIER / 2);
return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
}
public void setColor(int color) {
mPaint.setColor(color);
invalidateSelf();
}
static interface RoundRectHelper {
void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius, Paint paint);
}
}
@@ -15,10 +15,15 @@
package com.hippo.ehviewer.widget;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.Typeface;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v7.widget.CardView;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -27,31 +32,50 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.hippo.animation.SimpleAnimatorListener;
import com.hippo.drawable.AddDeleteDrawable;
import com.hippo.drawable.DrawerArrowDrawable;
import com.hippo.ehviewer.R;
import com.hippo.util.MathUtils;
import com.hippo.util.UiUtils;
import com.hippo.util.ViewUtils;
import com.hippo.widget.SimpleImageView;
import java.util.ArrayList;
import java.util.List;
public class SearchBar extends CardView implements View.OnClickListener,
TextView.OnEditorActionListener {
private static final long ANIMATION_TIME = 300;
private Path mPath = new Path();
private int mWidth;
private int mHeight;
private int mBaseHeight;
private float mProgress;
private SimpleImageView mMenuButton;
private TextView mLogoTextView;
private SimpleImageView mActionButton;
private SearchEditText mEditText;
private ListView mList;
private DrawerArrowDrawable mDrawerArrowDrawable;
private AddDeleteDrawable mAddDeleteDrawable;
private List<String> mSuggestionList;
private ArrayAdapter mSuggestionAdapter;
private Helper mHelper;
private boolean mInEditMode = false;
private boolean mInAnimation = false;
private boolean mFirstLayout = true;
public SearchBar(Context context) {
super(context);
@@ -71,6 +95,7 @@ public class SearchBar extends CardView implements View.OnClickListener,
private void init(Context context) {
setRadius(UiUtils.dp2pix(context, 2));
setCardElevation(UiUtils.dp2pix(context, 2));
setCardBackgroundColor(Color.WHITE);
setOnClickListener(this);
LayoutInflater.from(context).inflate(R.layout.widget_search_bar, this);
@@ -78,6 +103,7 @@ public class SearchBar extends CardView implements View.OnClickListener,
mLogoTextView = (TextView) findViewById(R.id.logo);
mActionButton = (SimpleImageView) findViewById(R.id.action);
mEditText = (SearchEditText) findViewById(R.id.search_edit_text);
mList = (ListView) findViewById(R.id.search_bar_list);
mDrawerArrowDrawable = new DrawerArrowDrawable(getContext());
mAddDeleteDrawable = new AddDeleteDrawable(getContext());
@@ -89,6 +115,15 @@ public class SearchBar extends CardView implements View.OnClickListener,
mActionButton.setOnClickListener(this);
mEditText.setSearchBar(this);
mEditText.setOnEditorActionListener(this);
mSuggestionList = new ArrayList<>();
mSuggestionList.add("aaaaaaaaaaaaaaaaaaaaa");
mSuggestionList.add("dfsgfewrewfes");
mSuggestionList.add("dehyklrthrhbdsrf");
mSuggestionList.add("粉色热污染份额为别人的");
mSuggestionAdapter = new ArrayAdapter<>(getContext(),
android.R.layout.simple_list_item_1, mSuggestionList);
mList.setAdapter(mSuggestionAdapter);
}
public void setHelper(Helper helper) {
@@ -144,12 +179,28 @@ public class SearchBar extends CardView implements View.OnClickListener,
oa1.setDuration(ANIMATION_TIME);
ObjectAnimator oa2 = ObjectAnimator.ofFloat(mAddDeleteDrawable, "progress", 0f, 1f);
oa2.setDuration(ANIMATION_TIME);
ObjectAnimator oa3 = ObjectAnimator.ofFloat(this, "progress", 0f, 1f);
oa3.setDuration(ANIMATION_TIME);
oa3.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
ViewUtils.setVisibility(mList, View.VISIBLE);
mInAnimation = true;
}
@Override
public void onAnimationEnd(Animator animation) {
mInAnimation = false;
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
oa1.setAutoCancel(true);
oa2.setAutoCancel(true);
oa3.setAutoCancel(true);
}
oa1.start();
oa2.start();
oa3.start();
}
}
@@ -167,12 +218,67 @@ public class SearchBar extends CardView implements View.OnClickListener,
oa1.setDuration(ANIMATION_TIME);
ObjectAnimator oa2 = ObjectAnimator.ofFloat(mAddDeleteDrawable, "progress", 1f, 0f);
oa2.setDuration(ANIMATION_TIME);
ObjectAnimator oa3 = ObjectAnimator.ofFloat(this, "progress", 1f, 0f);
oa3.setDuration(ANIMATION_TIME);
oa3.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
mInAnimation = true;
}
@Override
public void onAnimationEnd(Animator animation) {
ViewUtils.setVisibility(mList, View.GONE);
mInAnimation = false;
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
oa1.setAutoCancel(true);
oa2.setAutoCancel(true);
oa3.setAutoCancel(true);
}
oa1.start();
oa2.start();
oa3.start();
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mInEditMode) {
mWidth = right - left;
mHeight = bottom - top;
}
if (mFirstLayout) {
mFirstLayout = false;
mBaseHeight = bottom - top;
}
}
@SuppressWarnings("unused")
public void setProgress(float progress) {
mProgress = progress;
invalidate();
}
@SuppressWarnings("unused")
public float getProgress() {
return mProgress;
}
@Override
public void draw(@NonNull Canvas canvas) {
if (mInAnimation) {
final int state = canvas.save();
float bottom = MathUtils.lerp(mBaseHeight, mHeight, mProgress);
mPath.rewind();
mPath.addRect(0f, 0f, mWidth, bottom, Path.Direction.CW);
canvas.clipPath(mPath);
super.draw(canvas);
canvas.restoreToCount(state);
} else {
super.draw(canvas);
}
}