Clip Views:剪裁可以让我们很容易的去改变一个view的形状(圆,圆角矩形,矩形);
使用剪裁:
To define a custom outline for a view in your code:
1,Extend the ViewOutlineProvider class. 继承ViewOutlineProvider 类
2,Override the getOutline() method.重写getOutline()方法。
3,Assign the new outline provider to your view with the View.setOutlineProvider() method. 通过view.setOutlineProvider()设置provider。
重要说明:
Clipping views is an expensive operation, so don’t animate the shape you use to clip a view. To achieve this effect, use the Reveal Effect animation.
google告诉我们剪裁view是一个非常昂贵的操作,所以我们不能通过剪裁view来实现一个shape动画。我们可以使用Reveal Effect动画。
ClippingBasicFragment 的代码:
/**
* This sample shows how to clip a {@link View} using an {@link Outline}.
*/
public class ClippingBasicFragment extends Fragment {
private final static String TAG = "ClippingBasicFragment";
/* Store the click count so that we can show a different text on every click. */
private int mClickCount = 0;
/* The {@Link Outline} used to clip the image with. */
private ViewOutlineProvider mOutlineProvider;
/* An array of texts. */
private String[] mSampleTexts;
/* A reference to a {@Link TextView} that shows different text strings when clicked. */
private TextView mTextView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
mOutlineProvider = new ClipOutlineProvider();
mSampleTexts = getResources().getStringArray(R.array.sample_texts);
}
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.clipping_basic_fragment, container, false);
}
@Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
/* Set the initial text for the TextView. */
mTextView = (TextView) view.findViewById(R.id.text_view);
changeText();
final View clippedView = view.findViewById(R.id.frame);
/* Sets the OutlineProvider for the View. 开始就设置有默认效果。*/
clippedView.setOutlineProvider(mOutlineProvider);
clippedView.setClipToOutline(true);
/* When the button is clicked, the text is clipped or un-clipped. */
view.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View bt) {
clippedView.setOutlineProvider(mOutlineProvider);
// 这一步必须有
clippedView.invalidate();
/* // Toggle whether the View is clipped to the outline
if (clippedView.getClipToOutline()) {
The Outline is set for the View, but disable clipping.
clippedView.setClipToOutline(false);
Log.d(TAG, String.format("Clipping to outline is disabled"));
((Button) bt).setText(R.string.clip_button);
} else {
Enables clipping on the View.
clippedView.setClipToOutline(true);
clippedView.setOutlineProvider(mOutlineProvider);
Log.d(TAG, String.format("Clipping to outline is enabled"));
((Button) bt).setText(R.string.unclip_button);
}*/
}
});
view.findViewById(R.id.btn_oval).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View bt) {
ViewOutlineProvider vop = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
//这个其实就是在矩形里面截取正方形,然后在正方形里面取个小正方形。
int mlt = 100;
int cw = view.getHeight();
int ch = cw;
int mleft= (view.getWidth()-cw)/2;
int mtop = mlt;
cw = ch-mtop;
ch = cw;
int mright = mleft+cw;
int mbottom = ch+mtop;
android.util.Log.e(TAG, "mleft = "+mleft+" mtop = "+mtop+" mright = "+ mright+ " mbottom = "+ mbottom);
outline.setOval(mleft, mtop, mright, mbottom);
}
};
clippedView.setOutlineProvider(vop);
// 这一步必须有
clippedView.invalidate();
}
});
view.findViewById(R.id.btn_rect).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View bt) {
ViewOutlineProvider vop = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
final int margin = Math.min(view.getWidth(), view.getHeight()) / 10;
outline.setRect(margin, margin, view.getWidth() - margin, view.getHeight() - margin);
}
};
clippedView.setOutlineProvider(vop);
// 这一步必须有
clippedView.invalidate();
}
});
/* When the text is clicked, a new string is shown. */
view.findViewById(R.id.text_view).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mClickCount++;
// Update the text in the TextView
changeText();
// Invalidate the outline just in case the TextView changed size
clippedView.invalidateOutline();
}
});
}
private void changeText() {
// Compute the position of the string in the array using the number of strings
// and the number of clicks.
String newText = mSampleTexts[mClickCount % mSampleTexts.length];
/* Once the text is selected, change the TextView */
mTextView.setText(newText);
Log.d(TAG, String.format("Text was changed."));
}
/**
* A {@link ViewOutlineProvider} which clips the view with a rounded rectangle which is inset
* by 10%
*/
private class ClipOutlineProvider extends ViewOutlineProvider {
@Override
public void getOutline(View view, Outline outline) {
final int margin = Math.min(view.getWidth(), view.getHeight()) / 10;
outline.setRoundRect(margin, margin, view.getWidth() - margin, view.getHeight() - margin, margin / 2);
}
}
}
xml布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="@+id/frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/button"
android:addStatesFromChildren="true"
android:background="@drawable/gradient_drawable"
android:foreground="?android:attr/selectableItemBackground" >
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="64dp"
android:layout_marginRight="64dp"
android:gravity="center"
android:textColor="@android:color/white"
android:textSize="26sp" />
</FrameLayout>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:text="round"
android:textAllCaps="false" />
<Button
android:id="@+id/btn_oval"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button"
android:layout_alignBottom="@+id/button"
android:layout_toRightOf="@+id/button"
android:text="oval"
android:textAllCaps="false" />
<Button
android:id="@+id/btn_rect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button"
android:layout_alignBottom="@+id/button"
android:layout_toLeftOf="@+id/button"
android:text="rectrang"
android:textAllCaps="false" />
</RelativeLayout>
下面来看看相关类的源码:
ViewOutlineProvider.java :提供几个静态的成员(对应xml中的属性的value值)
就是它 android:clipToOutline
/*
* 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.view;
import android.graphics.Outline;
import android.graphics.drawable.Drawable;
/**
* Interface by which a View builds its {@link Outline}, used for shadow casting and clipping.
*/
public abstract class ViewOutlineProvider {
/**
* Default outline provider for Views, which queries the Outline from the View's background,
* or generates a 0 alpha, rectangular Outline the size of the View if a background
* isn't present.
*
* @see Drawable#getOutline(Outline)
*/
public static final ViewOutlineProvider BACKGROUND = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
Drawable background = view.getBackground();
if (background != null) {
background.getOutline(outline);
} else {
outline.setRect(0, 0, view.getWidth(), view.getHeight());
outline.setAlpha(0.0f);
}
}
};
/**
* Maintains the outline of the View to match its rectangular bounds,
* at <code>1.0f</code> alpha.
*
* This can be used to enable Views that are opaque but lacking a background cast a shadow.
*/
public static final ViewOutlineProvider BOUNDS = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRect(0, 0, view.getWidth(), view.getHeight());
}
};
/**
* Maintains the outline of the View to match its rectangular padded bounds,
* at <code>1.0f</code> alpha.
*
* This can be used to enable Views that are opaque but lacking a background cast a shadow.
*/
public static final ViewOutlineProvider PADDED_BOUNDS = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRect(view.getPaddingLeft(),
view.getPaddingTop(),
view.getWidth() - view.getPaddingRight(),
view.getHeight() - view.getPaddingBottom());
}
};
/**
* Called to get the provider to populate the Outline.
*
* This method will be called by a View when its owned Drawables are invalidated, when the
* View's size changes, or if {@link View#invalidateOutline()} is called
* explicitly.
*
* The input outline is empty and has an alpha of <code>1.0f</code>.
*
* @param view The view building the outline.
* @param outline The empty outline to be populated.
*/
public abstract void getOutline(View view, Outline outline);
}
Outline.java 源码:
/*
* 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.graphics;
import android.annotation.NonNull;
import android.graphics.drawable.Drawable;
/**
* Defines a simple shape, used for bounding graphical regions.
* <p>
* Can be computed for a View, or computed by a Drawable, to drive the shape of
* shadows cast by a View, or to clip the contents of the View.
*
* @see android.view.ViewOutlineProvider
* @see android.view.View#setOutlineProvider(android.view.ViewOutlineProvider)
* @see Drawable#getOutline(Outline)
*/
public final class Outline {
/** @hide */
public Path mPath;
/** @hide */
public Rect mRect;
/** @hide */
public float mRadius;
/** @hide */
public float mAlpha;
/**
* Constructs an empty Outline. Call one of the setter methods to make
* the outline valid for use with a View.
*/
public Outline() {}
/**
* Constructs an Outline with a copy of the data in src.
*/
public Outline(@NonNull Outline src) {
set(src);
}
/**
* Sets the outline to be empty.
*
* @see #isEmpty()
*/
public void setEmpty() {
mPath = null;
mRect = null;
mRadius = 0;
}
/**
* Returns whether the Outline is empty.
* <p>
* Outlines are empty when constructed, or if {@link #setEmpty()} is called,
* until a setter method is called
*
* @see #setEmpty()
*/
public boolean isEmpty() {
return mRect == null && mPath == null;
}
/**
* Returns whether the outline can be used to clip a View.
* <p>
* Currently, only Outlines that can be represented as a rectangle, circle,
* or round rect support clipping.
*
* @see {@link android.view.View#setClipToOutline(boolean)}
*/
public boolean canClip() {
return !isEmpty() && mRect != null;
}
/**
* Sets the alpha represented by the Outline - the degree to which the
* producer is guaranteed to be opaque over the Outline's shape.
* <p>
* An alpha value of <code>0.0f</code> either represents completely
* transparent content, or content that isn't guaranteed to fill the shape
* it publishes.
* <p>
* Content producing a fully opaque (alpha = <code>1.0f</code>) outline is
* assumed by the drawing system to fully cover content beneath it,
* meaning content beneath may be optimized away.
*/
public void setAlpha(float alpha) {
mAlpha = alpha;
}
/**
* Returns the alpha represented by the Outline.
*/
public float getAlpha() {
return mAlpha;
}
/**
* Replace the contents of this Outline with the contents of src.
*
* @param src Source outline to copy from.
*/
public void set(@NonNull Outline src) {
if (src.mPath != null) {
if (mPath == null) {
mPath = new Path();
}
mPath.set(src.mPath);
mRect = null;
}
if (src.mRect != null) {
if (mRect == null) {
mRect = new Rect();
}
mRect.set(src.mRect);
}
mRadius = src.mRadius;
mAlpha = src.mAlpha;
}
/**
* Sets the Outline to the rounded rect defined by the input rect, and
* corner radius.
*/
public void setRect(int left, int top, int right, int bottom) {
setRoundRect(left, top, right, bottom, 0.0f);
}
/**
* Convenience for {@link #setRect(int, int, int, int)}
*/
public void setRect(@NonNull Rect rect) {
setRect(rect.left, rect.top, rect.right, rect.bottom);
}
/**
* Sets the Outline to the rounded rect defined by the input rect, and corner radius.
* <p>
* Passing a zero radius is equivalent to calling {@link #setRect(int, int, int, int)}
*/
public void setRoundRect(int left, int top, int right, int bottom, float radius) {
if (left >= right || top >= bottom) {
setEmpty();
return;
}
if (mRect == null) mRect = new Rect();
mRect.set(left, top, right, bottom);
mRadius = radius;
mPath = null;
}
/**
* Convenience for {@link #setRoundRect(int, int, int, int, float)}
*/
public void setRoundRect(@NonNull Rect rect, float radius) {
setRoundRect(rect.left, rect.top, rect.right, rect.bottom, radius);
}
/**
* Sets the outline to the oval defined by input rect.
*/
public void setOval(int left, int top, int right, int bottom) {
if (left >= right || top >= bottom) {
setEmpty();
return;
}
if ((bottom - top) == (right - left)) {
// represent circle as round rect, for efficiency, and to enable clipping
setRoundRect(left, top, right, bottom, (bottom - top) / 2.0f);
return;
}
if (mPath == null) mPath = new Path();
mPath.reset();
mPath.addOval(left, top, right, bottom, Path.Direction.CW);
mRect = null;
}
/**
* Convenience for {@link #setOval(int, int, int, int)}
*/
public void setOval(@NonNull Rect rect) {
setOval(rect.left, rect.top, rect.right, rect.bottom);
}
/**
* Sets the Constructs an Outline from a
* {@link android.graphics.Path#isConvex() convex path}.
*/
public void setConvexPath(@NonNull Path convexPath) {
if (convexPath.isEmpty()) {
setEmpty();
return;
}
if (!convexPath.isConvex()) {
throw new IllegalArgumentException("path must be convex");
}
if (mPath == null) mPath = new Path();
mPath.set(convexPath);
mRect = null;
mRadius = -1.0f;
}
}