AndroidMaterialDesign_ClipViews

本文介绍如何使用Android的ViewOutlineProvider进行视图剪裁,包括圆形、圆角矩形及矩形等形状,并展示了ClippingBasicFragment的具体实现,同时提供了ViewOutlineProvider与Outline的源码解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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>

demo下载

下面来看看相关类的源码:
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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值