android 镂空字体下载,Android——自定义镂空掩饰控件

刚学完ViewDragHelper和PorterDuffXferMode的我,突然想做一个这样效果的自定义控件:点击ListView的列表项,通过ViewDragHelper用动画方式上下各弹出一个控件遮盖住ListView,这两个控件在遮盖listView的过程中有一部分是镂空的。先上效果图:

170447892.gif

首先是进行页面的布局,让自定义控件PlayLayout继承自Franlayout,在最底层放的就是listView所在的子FramLayout(Id:midContent),然后依次在上面加上下两个看起来被分割的FrameLayout(这里直接写了自定义控件SplitLayout继承Framlayout,id分别为:topSplit,bottomSplit),最后还要放上旋转圆的部分(自定义控件ControlPanel,)

大致布局像这样:

//显示ListView的底层部分

//顶部的分割区

//底部分割

//旋转圆部分

首先分析怎么设置上下SplitLayout弹出的工作:

1、在PlayLayout中的onFinishInflate()中获得各个子布局

midContent = (ViewGroup) getChildAt(0);

topSplit = (SplitLayout) getChildAt(1);

bottomSplit = (SplitLayout) getChildAt(2);

controlPanel = (ControlPanel) getChildAt(3);

//SplitLayout.Position是一个enum 标志该SplitLayout是上半部分还是下半部分,默认下半部分

topSplit.setPosition(SplitLayout.Position.TOP);

2、根据可以设置的splitScale即上下splitLayout高度比(默认0.5,上下各一半),在PlayLayout控件的onMeaure中设置他们的高度

int count = getChildCount();

for (int i = 0; i < count; i++) {

//测量子控件

measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);

}

int w = MeasureSpec.getSize(widthMeasureSpec);

int h = MeasureSpec.getSize(heightMeasureSpec);

//根据分割比例计算上下分割控件各自的高度

int topH = (int) (h * 1.0f * splitScale + 0.5f);

int bottomH = h - topH;

ViewGroup.LayoutParams params1 = topSplit.getLayoutParams();

ViewGroup.LayoutParams params2 = bottomSplit.getLayoutParams();

ViewGroup.LayoutParams params3 = controlPanel.getLayoutParams();

params1.height = topH;

params1.width = getMeasuredWidth();

params2.height = bottomH;

params2.width = getMeasuredWidth();

params3.height = (int) (radias*2);

params3.width = (int) (radias*2) ;

//设置各自的高度

topSplit.setLayoutParams(params1);//上半部分割控件

bottomSplit.setLayoutParams(params2);//下半部分

controlPanel.setLayoutParams(params3);//旋转圆盘

3.重写PlayLayout的onLayout方法,进行子控件的布局

int h = getMeasuredHeight();

int topH = (int) (h * 1.0f * splitScale + 0.5f);

int bottomH = h - topH;

//放置listview的底层的midContent正常的layout

midContent.layout(left, top, right, bottom);

//topSplit一开始我们让其在最上面隐藏,点击listItem后才出来

topSplit.layout(left, -topH, right, 0);

//同样,bottomSplit最开始让其在父控件的底部隐藏

bottomSplit.layout(left, h, right, h + bottomH);

controlPanel.layout((int)(getMeasuredWidth()*1.0/2-radias),

(int)(topH-radias),

(int)(getMeasuredWidth()*1.0/2+radias),

(int) (topH+ radias));

4.通过ViewDragHelper来处理滚动,接下来是ViewDragHelper的一般过程

先初始化

public enum Status {//一共三个状态,打开,关闭,正在拖动

OPEN, DRAGING, CLOSE

}

private Status status = Status.CLOSE;

private ViewDragHelper mDragHelp;

//初始化

private void initView() {

mDragHelp = ViewDragHelper.create(this, mDragCallbak);

status = Status.CLOSE;//初始化状态,splitLayout和controlPanel处于关闭状态

}

当size发生变化时,重置一些参数

//这里是设置底部的splitLayout可以拖拽

private int mTop;//当前拖拽的位置

private int mStartTop;//初始Y方向top位置

private int mEndTop;//打开后,拖拽到的位置

private int mRange;//可以拖动的范围

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

int hh = getMeasuredHeight();

int topH = (int) (hh * 1.0f * splitScale + 0.5f);

mStartTop = hh;//最开始是在底部

mEndTop = topH;//能够最终拖拽到父控件顶部splitLayout高度大小的位置

mTop = hh;//初始化当前位置

mRange = mStartTop - mEndTop;//计算拖拽范围

}

拦截时间,以及处理事件

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

if (status == Status.CLOSE) return false;

return mDragHelp.shouldInterceptTouchEvent(ev);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

mDragHelp.processTouchEvent(event);

return true;

}

初始化ViewDragHelper.Callback

private ViewDragHelper.Callback mDragCallbak = new ViewDragHelper.Callback() {

@Override

public boolean tryCaptureView(View child, int pointerId) {

return child == bottomSplit;//选择能够拖动的控件

}

@Override

public int clampViewPositionVertical(View child, int top, int dy) {

//设置当前的拖拽位置,要在拖动范围之内

if (top > mStartTop)

top = mStartTop;

if (top < mEndTop)

top = mEndTop;

return top;

}

//当拖动的控件位置发生变化时,调用该函数,在函数里更新当前的top位置mTop

@Override

public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {

super.onViewPositionChanged(changedView, left, top, dx, dy);

mTop += dy;

mTop = Math.max(Math.min(mTop, mStartTop), mEndTop);

dispatchScrollEvent();

}

//设置拖动范围

@Override

public int getViewVerticalDragRange(View child) {

return mRange;

}

//当拖拽放开时

@Override

public void onViewReleased(View releasedChild, float xvel, float yvel) {

if (mTop < mEndTop + mRange / 2) {

open();

} else {

close();

}

ViewCompat.postInvalidateOnAnimation(PlayLayout.this);

}

};

还需要重写onComputeScroll(和Scroll.startScrolll方法一样)

@Override

public void computeScroll() {

super.computeScroll();

if (mDragHelp.continueSettling(true))

ViewCompat.postInvalidateOnAnimation(this);

}

ViewDragHelper的惯性操作都放在open和close

public void open() {

mDragHelp.smoothSlideViewTo(bottomSplit, 0, mEndTop);

ViewCompat.postInvalidateOnAnimation(this);

}

private void close() {

mDragHelp.smoothSlideViewTo(bottomSplit, 0, mStartTop);

ViewCompat.postInvalidateOnAnimation(this);

}

其中在ViewDragHelper.Callback的onViewPositionChanged中每次调用我们都计算了当前top,并分发了事件:

private Status updateStatus() {

if (mTop <= mEndTop + 5) {

status = Status.OPEN;

System.out.println("status->>" + "open");

} else if (mTop >= mStartTop - 5) {

status = Status.CLOSE;

System.out.println("status->>" + "close");

} else {

status = Status.DRAGING;

}

return status;

}

private void dispatchScrollEvent() {

updateStatus();//根据当前mTop计算状态

//计算拖拽完成的程度

float percent = (mStartTop - mTop) * 1.0f / mRange;

//根据百分比,移动呈现顶部splitLayout

ViewHelper.setTranslationY(topSplit, percent * 1.0f * mEndTop + 0.5f);

ViewHelper.setScaleY(controlPanel, percent);

ViewHelper.setScaleX(controlPanel, percent);

ViewHelper.setRotation(controlPanel, (1 - percent) *360);

}

5.通过PorterDuffXferMode遮罩层设置镂空的SplitLayout

这里是在SplitLayout控件中

首先初始化

private PorterDuffXfermode mMode;

private Paint mPaint;

private Bitmap mBg;//控件背景

private void initView() {

mPaint = new Paint();

mPaint.setAlpha(0);//镂空遮罩层画笔一定要透明

mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

}

然后重写onDraw时这样处理

int w = getMeasuredWidth();

int h = getMeasuredHeight();

Bitmap bm = null;

if (mBg != null) {

int bitW = mBg.getWidth();

int bitH = mBg.getHeight();

//因为该控件只显示父控件一定比例的高度,所以背景比例是按父控件高度计算的

float scaleH = h * 1.0f / splitScale / bitH;//h*(1/splitScale)得到父控件高度

float scaleW = w * 1.0f / bitW;

Matrix matrix = new Matrix();

matrix.setScale(scaleW, scaleH);

bm = Bitmap.createBitmap(mBg, 0, 0, bitW, bitH, matrix, true);

}

int startDegree = mPosition == Position.TOP ? 180 : 0;

//利用同样大小的bitMap做镂空层

Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);

Canvas canvas1 = new Canvas(bitmap);

//根据Position决定绘制已经缩放图像的上半还是下半,在下半则起点为父控件高度减去自己的高度

int t = mPosition == Position.TOP ? 0 : (int) (h * 1.0f / splitScale - h);

if (mBg == null)

canvas1.drawColor(Color.argb(200, 128, 128, 128));

else

canvas1.drawBitmap(bm,

new Rect(0, t, w, t + h),

new Rect(0, 0, w, h),

null);

//设置镂空圆的位置

float top = h - radias;

if (mPosition == Position.BOTTOM)

top = -radias;

float bottom = top + 2 * radias;

RectF rectF = new RectF(w / 2 - radias, top, w / 2 + radias, bottom);

//上面再bitmap上已经画了背景图片,再通过带xferMode属性的透明画笔去镂空背景图片,让其显示控件下面一层的控件视图

canvas1.drawArc(rectF, startDegree, 180, true, mPaint);//注意mPaint的特殊设置,alpha=0,xmode(Mode.SRC_In);

//将镂空层画到背景上

canvas.drawBitmap(bitmap, 0, 0, null);

旋转圆控件的onDraw处理和SplitLayout基本一样,除了要镂空

int bitW = mBg.getWidth();

int bitH = mBg.getHeight();

System.out.println("sout->>bitH=" + bitH + " bitW=" + bitW);

//计算缩放比例

float scaleH = h * 1.0f / bitH;

float scaleW = w * 1.0f / bitW;

Matrix matrix = new Matrix();

matrix.setScale(scaleW, scaleH);

Bitmap bitmap=Bitmap.createBitmap(mBg,0,0,bitW,bitH,matrix,true);

mShader=new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);//通过BitmapShader很容易自定义各种什么圆角,圆形背景控件

mPaint.setShader(mShader);

canvas.drawCircle(w/2,h/2,radias,mPaint);

注意:要想实现镂空还得,必须在layout:xml布局文件里面指定background如下:

android:background="#00ffffff"

android:id="@+id/topSplit"

android:layout_width="match_parent"

android:layout_height="wrap_content">

这里也有个问题搞不懂?

当我这这样设置时镂空的地方成了黑色,也不显示下层控件

android:background="#ffffff"

android:alpha="0"

android:id="@+id/topSplit"

android:layout_width="match_parent"

android:layout_height="wrap_content">

(android:background=”#00ffffff”)和(android:background=”#ffffff” android:alpha=”0”) 不应该是一样的吗

demo源代码地址(github新手,文件在app module 里面):

https://github.com/yifantao/SkyMusic

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值