今天我接着站在了巨人的肩膀上了!因为上次我参考大神利用 Path 实现了点九图效果,附上我上次讲解链接: .这一次,我将完成上次说的实现点击toolbar按钮弹出菜单效果,当然还是利用 Path 来完成这个效果.如果想看巨人的巨作,请点击: ,我此次仅仅是将大神的点九图更换为一个由Path完成的效果而已.
首先,我们还是看下我完成的结果吧!
嘿嘿,和大神的一样哈!是的,我只是改了背景图而已.这样我们就可以随便刷不要UI哥帮忙了,而且那个三角凸起可以 左右位置调整 哦!
首先还是看下MainActivity的布局吧
复制代码
这个就不说啥了,接着,来重点了,看 Recyclerview 了,因为大神用的是这个控件,那我只是给这个控件换了个衣服!
package com.jooyerbubblemenu;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.RectF;import android.support.annotation.Nullable;import android.support.v7.widget.RecyclerView;import android.util.AttributeSet;/** * Created by Jooyer on 2017/3/19 */public class JooyerBubbleRecyclerView extends RecyclerView{ private JooyerBubbleDrawable mBubbleDrawable; private float mArrowWidth; private float mArrowHeight; private float mRadius; private float mArrowOffset; private int mBubbleColor; private JooyerBubbleDrawable.ArrowDirection mArrowDirection; private boolean mArrowCenter; public JooyerBubbleRecyclerView(Context context) { this(context,null); } public JooyerBubbleRecyclerView(Context context, @Nullable AttributeSet attrs) { this(context, attrs,0); } public JooyerBubbleRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context,attrs); } private void init(Context context, AttributeSet attrs) { TypedArray array = context.obtainStyledAttributes(R.styleable.JooyerBubbleRecyclerView); mArrowWidth = array.getDimension(R.styleable.JooyerBubbleRecyclerView_jooyer_bubble_arrow_width, JooyerBubbleDrawable.Builder.DEFAULT_ARROW_WIDTH); mArrowHeight = array.getDimension(R.styleable.JooyerBubbleRecyclerView_jooyer_bubble_arrow_height, JooyerBubbleDrawable.Builder.DEFAULT_ARROW_HEIGHT); mRadius = array.getDimension(R.styleable.JooyerBubbleRecyclerView_jooyer_bubble_arrow_radius, JooyerBubbleDrawable.Builder.DEFAULT_RADIUS); int location = array.getInt(R.styleable.JooyerBubbleRecyclerView_jooyer_bubble_arrow_direction, 0); mArrowDirection = JooyerBubbleDrawable.ArrowDirection.mapIntToValue(location); mArrowOffset = array.getDimension(R.styleable.JooyerBubbleRecyclerView_jooyer_bubble_arrow_offset, JooyerBubbleDrawable.Builder.DEFAULT_ARROW_OFFSET); mBubbleColor = array.getColor(R.styleable.JooyerBubbleRecyclerView_jooyer_bubble_arrow_color, JooyerBubbleDrawable.Builder.DEFAULT_BUBBLE_COLOR); mArrowCenter = array.getBoolean(R.styleable.JooyerBubbleRecyclerView_jooyer_bubble_arrow_center,false); array.recycle(); setPadding(); } private void setPadding() { int left = getPaddingLeft(); int top = getPaddingTop(); int right = getPaddingRight(); int bottom = getPaddingBottom(); switch (mArrowDirection){ case LEFT: left +=mArrowWidth; break; case TOP: top += mArrowHeight; break; case RIGHT: right += mArrowWidth; break; case BOTTOM: bottom +=mArrowHeight; break; } setPadding(left,top,right,bottom); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (w >0 || h > 0) setUp(w,h); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); setUp(getWidth(),getHeight()); } @Override public void onDraw(Canvas c) { if (null != mBubbleDrawable){ mBubbleDrawable.draw(c); } super.onDraw(c); } private void setUp(int width,int height){ setUp(0,0,width,height); } public void setUp(int left,int top,int right,int bottom){ mBubbleDrawable = new JooyerBubbleDrawable.Builder() .rect(new RectF(left,top,right,bottom)) .arrowWidth(mArrowWidth) .arrowHeight(mArrowHeight) .arrowOffset(mArrowOffset) .arrowCenter(mArrowCenter) .arrowDirection(mArrowDirection) .radius(mRadius) .bubbleColor(mBubbleColor) .build(); } public void setArrowOffset(float offset){ mArrowOffset = offset; }}复制代码没有什么注释,起始和上一篇文章自定义一样的,所以我们主要就是熟悉这种方式而已!我们还是瞅一眼 JooyerBubbleDrawable
package com.jooyerbubblemenu;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.ColorFilter;import android.graphics.Paint;import android.graphics.Path;import android.graphics.PixelFormat;import android.graphics.RectF;import android.graphics.drawable.Drawable;import android.util.Log;/** * Created by Jooyer on 2017/3/11 */public class JooyerBubbleDrawable extends Drawable { private static final String TAG = JooyerBubbleDrawable.class.getSimpleName(); /** * 保存坐标(自定义控件的大小) */ private RectF mRect; /** * 气泡的路径 */ private Path mPath = new Path(); private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); /** * 箭头宽度 */ private float mArrowWidth; /** * 箭头宽度 */ private float mArrowHeight; /** * 圆弧半径 */ private float mRadius; /** * 箭头所在位置偏移量 */ private float mArrowOffset; /** * 气泡背景色 */ private int mBubbleColor; /** * 三角箭头所在位置 */ private ArrowDirection mArrowDirection; /** * 箭头是否居中 */ private boolean mArrowCenter; /** * 重写此方法,在这里实现和 自定义控件中 onDraw 类似的功能 */ @Override public void draw(Canvas canvas) { mPaint.setColor(mBubbleColor); setUpPath(mArrowDirection, mPath); canvas.drawPath(mPath, mPaint); } @Override public void setAlpha(int alpha) { mPaint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter colorFilter) { mPaint.setColorFilter(colorFilter); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; //窗口透明化 } private void setUpPath(ArrowDirection arrowDirection, Path path) { switch (arrowDirection) { case LEFT: setUpLeftPath(mRect, path); break; case TOP: setUpTopPath(mRect, path); break; case RIGHT: setUpRightPath(mRect, path); break; case BOTTOM: setUpBottomPath(mRect,path); break; } } /** * 箭头朝左 */ private void setUpLeftPath(RectF rect, Path path) { if (mArrowCenter) mArrowOffset = (rect.bottom - rect.top - mArrowWidth) / 2; path.moveTo(rect.left + mArrowWidth + mRadius, rect.top); path.lineTo(rect.width() - mRadius, rect.top); // 这里的rect.width() 是可以使用rect.right Log.i(TAG, "====setUpLeftPath========" + (rect.width() - mRadius) + "======= : " + (rect.right - mRadius)); path.arcTo(new RectF(rect.right - mRadius, rect.top, rect.right, rect.top + mRadius), 270, 90); path.lineTo(rect.right, rect.bottom - mRadius); path.arcTo(new RectF(rect.right - mRadius, mRect.bottom - mRadius, rect.right, rect.bottom), 0, 90); path.lineTo(rect.left + mArrowWidth + mRadius, rect.bottom); path.arcTo(new RectF(rect.left + mArrowWidth, rect.bottom - mRadius, rect.left + mArrowWidth + mRadius, rect.bottom), 90, 90); path.lineTo(rect.left + mArrowWidth, mArrowHeight + mArrowOffset); path.lineTo(rect.left, mArrowOffset + mArrowHeight / 2); path.lineTo(rect.left + mArrowWidth, mArrowOffset); path.lineTo(rect.left + mArrowWidth, rect.top + mRadius); path.arcTo(new RectF(rect.left + mArrowWidth, mRect.top, rect.left + mArrowWidth + mRadius, rect.top + mRadius), 180, 90); path.close(); } /** * 箭头朝上 */ private void setUpTopPath(RectF rect, Path path) { if (mArrowCenter) mArrowOffset = (rect.right - rect.left - mArrowWidth) / 2; path.moveTo(rect.left + Math.min(mRadius, mArrowOffset), rect.top + mArrowHeight); path.lineTo(rect.left + mArrowOffset, rect.top + mArrowHeight); path.lineTo(rect.left + mArrowOffset + mArrowWidth / 2, rect.top); path.lineTo(rect.left + mArrowOffset + mArrowWidth, rect.top + mArrowHeight); path.lineTo(rect.right - mRadius, rect.top + mArrowHeight); path.arcTo(new RectF(rect.right - mRadius, rect.top + mArrowHeight, rect.right, rect.top + mArrowHeight + mRadius), 270, 90); path.lineTo(rect.right, rect.bottom - mRadius); path.arcTo(new RectF(rect.right - mRadius, rect.bottom - mRadius, rect.right, rect.bottom), 0, 90); path.lineTo(rect.left + mRadius, rect.bottom); path.arcTo(new RectF(rect.left, rect.bottom - mRadius, rect.left + mRadius, rect.bottom), 90, 90); path.lineTo(rect.left, rect.top + mArrowHeight + mRadius); path.arcTo(new RectF(rect.left, rect.top + mArrowHeight, rect.left + mRadius, rect.top + mArrowHeight + mRadius), 180, 90); path.close(); } /** * 箭头朝右 */ private void setUpRightPath(RectF rect, Path path) { if (mArrowCenter) mArrowOffset = (rect.bottom - rect.top - mArrowWidth) / 2; path.moveTo(rect.left + mRadius, rect.top); path.lineTo(rect.right - mRadius - mArrowWidth, rect.top); path.arcTo(new RectF(rect.right - mArrowWidth - mRadius, rect.top, rect.right - mArrowWidth, rect.top + mRadius), 270, 90); path.lineTo(rect.right - mArrowWidth, rect.top + mArrowOffset); path.lineTo(rect.right, rect.top + mArrowOffset + mArrowHeight / 2); path.lineTo(rect.right - mArrowWidth, rect.top + mArrowOffset + mArrowHeight); path.lineTo(rect.right - mArrowWidth, rect.bottom - mRadius); path.arcTo(new RectF(rect.right - mArrowWidth - mRadius, rect.bottom - mRadius, rect.right - mArrowWidth, rect.bottom), 0, 90); path.lineTo(rect.right - mArrowWidth - mRadius, rect.bottom); path.arcTo(new RectF(rect.left, rect.bottom - mRadius, rect.left + mRadius, rect.bottom), 90, 90); path.lineTo(rect.left, rect.top + mRadius); path.arcTo(new RectF(rect.left, rect.top, rect.left + mRadius, rect.top + mRadius), 180, 90); path.close(); } /** * 箭头朝下 */ private void setUpBottomPath(RectF rect, Path path) { if (mArrowCenter) mArrowOffset = (rect.right - rect.left - mArrowWidth) / 2; path.moveTo(rect.left + mRadius, rect.top); path.lineTo(rect.right - mRadius, rect.top); path.arcTo(new RectF(rect.right - mRadius, rect.top, rect.right, rect.top + mRadius), 270, 90); path.lineTo(rect.right, rect.bottom - mArrowHeight - mRadius); path.arcTo(new RectF(rect.right - mRadius, rect.bottom - mArrowHeight - mRadius, rect.right, rect.bottom - mArrowHeight), 0, 90); path.lineTo(rect.left + mArrowOffset + mArrowWidth, rect.bottom - mArrowHeight); path.lineTo(rect.left + mArrowOffset + mArrowWidth / 2, rect.bottom); path.lineTo(rect.left + mArrowOffset, rect.bottom - mArrowHeight); path.lineTo(rect.left + mRadius, rect.bottom - mArrowHeight); path.arcTo(new RectF(rect.left, rect.bottom - mArrowHeight - mRadius, rect.left + mRadius, rect.bottom - mArrowHeight), 90, 90); path.lineTo(rect.left, rect.top + mRadius); path.arcTo(new RectF(rect.left, rect.top,rect.left + mRadius,rect.top + mRadius),180,90); path.close(); } private JooyerBubbleDrawable(Builder builder) { this.mRect = builder.mRectF; this.mRadius = builder.mRadius; this.mArrowWidth = builder.mArrowWidth; this.mArrowHeight = builder.mArrowHeight; this.mArrowOffset = builder.mArrowOffset; this.mBubbleColor = builder.mBubbleColor; this.mArrowDirection = builder.mArrowDirection; this.mArrowCenter = builder.mArrowCenter; } /** * 建造者模式 */ public static class Builder { /** * 箭头默认宽度 */ public static float DEFAULT_ARROW_WIDTH = 25; /** * 箭头默认高度 */ public static float DEFAULT_ARROW_HEIGHT = 25; /** * 默认圆角半径 */ public static float DEFAULT_RADIUS = 20; /** * 默认箭头偏移量 */ public static float DEFAULT_ARROW_OFFSET = 50; /** * 气泡默认背景颜色 */ public static int DEFAULT_BUBBLE_COLOR = Color.RED; private RectF mRectF; private float mArrowWidth = DEFAULT_ARROW_WIDTH; private float mArrowHeight = DEFAULT_ARROW_HEIGHT; private float mRadius = DEFAULT_RADIUS; private float mArrowOffset = DEFAULT_ARROW_OFFSET; private int mBubbleColor = DEFAULT_BUBBLE_COLOR; private ArrowDirection mArrowDirection = ArrowDirection.LEFT; private boolean mArrowCenter; public Builder rect(RectF rect) { this.mRectF = rect; return this; } public Builder arrowWidth(float width) { this.mArrowWidth = width; return this; } public Builder arrowHeight(float height) { this.mArrowHeight = height; return this; } public Builder radius(float angle) { this.mRadius = angle; //TODO return this; } public Builder arrowOffset(float position) { this.mArrowOffset = position; return this; } public Builder bubbleColor(int color) { this.mBubbleColor = color; return this; } public Builder arrowDirection(ArrowDirection direction) { this.mArrowDirection = direction; return this; } public Builder arrowCenter(boolean arrowCenter) { this.mArrowCenter = arrowCenter; return this; } public JooyerBubbleDrawable build() { if (null == mRectF) { throw new IllegalArgumentException("BubbleDrawable RectF can not be null"); } return new JooyerBubbleDrawable(this); } } /** * 箭头位置 */ public enum ArrowDirection { LEFT(0x00), TOP(0x01), RIGHT(0x02), BOTTOM(0x03); private int mValue; ArrowDirection(int value) { mValue = value; } private int getIntValue() { return mValue; } public static ArrowDirection getDefault() { return LEFT; } public static ArrowDirection mapIntToValue(int stateInt) { for (ArrowDirection value : ArrowDirection.values()) { if (stateInt == value.getIntValue()) { return value; } } return getDefault(); } }}复制代码自定义属性
复制代码
我们看下它的具体用法:
复制代码
接下来,我们看看大神封装的Item和PopupWindow
package com.jooyerbubblemenu;/** * 每一项菜单 * Created by Jooyer on 2017/2/10 */public class MenuItem { private int icon; private String content; public MenuItem() { } public MenuItem(int icon, String content) { this.icon = icon; this.content = content; } public int getIcon() { return icon; } public void setIcon(int icon) { this.icon = icon; } public String getContent() { return content; } public void setContent(String content) { this.content = content; }}复制代码
大神的神剑: TopRightMenu
package com.jooyerbubblemenu;import android.animation.ValueAnimator;import android.app.Activity;import android.content.Context;import android.graphics.Point;import android.graphics.Rect;import android.graphics.drawable.ColorDrawable;import android.support.v7.widget.LinearLayoutManager;import android.support.v7.widget.RecyclerView;import android.view.LayoutInflater;import android.view.View;import android.view.WindowManager;import android.widget.PopupWindow;import com.jooyerbubblemenu.utils.LogUtils;import java.util.ArrayList;import java.util.List;/** * 封装一个PopupWindow 实现类似QQ,支付宝等右上角弹框效果 * Created by Jooyer on 2017/2/10 */public class TopRightMenu { private static final String TAG = "TopRightMenu"; private static final int DEFAULT_AMEND = 200; private Context mContext; private PopupWindow mPopupWindow; private RecyclerView mRecyclerView; private View mParent; private TopRightMenuAdapter mTopRightMenuAdapter; private List以上我在不明白的地方打印有日志输入,可以了解大神用意最后来看哈使用方式:
package com.jooyerbubblemenu;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.ImageView;import android.widget.Toast;import java.util.ArrayList;import java.util.List;public class MainActivity extends AppCompatActivity { private ImageView more; private TopRightMenu mTopRightMenu; private ListmItems; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initMenu(); more = (ImageView) findViewById(R.id.more); mTopRightMenu = new TopRightMenu(this); more.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mTopRightMenu.setWidth(400) .setHeight(600) .setShowIcon(true) .setShowAnimationStyle(true) .setAnimationStyle(R.style.TopRightMenu_Anim) .setShowBackground(true) .setArrowPosition(345f) .addMenuItems(mItems) .addMenuItem(new MenuItem(R.mipmap.facetoface,"面对面快传")) .addMenuItem(new MenuItem(R.mipmap.pay,"付款")) .setOnTopRightMenuItemClickListener(new OnTopRightMenuItemClickListener() { @Override public void onTopRightMenuItemClick(int position) { Toast.makeText(MainActivity.this, " 点击位置 :" + position, Toast.LENGTH_SHORT).show(); } }); mTopRightMenu.show(v,null,null); } }); } private void initMenu() { mItems = new ArrayList<>(); mItems.add(new MenuItem(R.mipmap.multichat, "发起多人聊天")); mItems.add(new MenuItem(R.mipmap.addmember, "加好友")); mItems.add(new MenuItem(R.mipmap.qr_scan, "扫一扫")); }}复制代码
其运行结果,如开头所示,这里主要是对上次 Path 的学习的巩固!下一次,我将继续站在大神肩膀上大家分享
附上本次 github 地址: