恭喜发财! — 手把手教你仿造一个qq下拉抢红包 Android自定义view

最后更新于:2022-04-01 10:01:46

### 猴年猴赛雷啊各位,今天没吃药我感觉自己萌萌哒! qq和微信和支付宝红包大战,不知道各位的战绩是多少嘞? 反正我qq抢到的都是气泡。因为太不爽,所以自己写一个下拉抢红包自己玩(自己跟自己玩)。 先来看效果图。这个…… 呃~~ -**__**-” 。。有点丑 是低仿。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaeab10494.jpg) 转载请注明出处:[http://blog.csdn.net/wingichoy/article/details/50662592](http://blog.csdn.net/wingichoy/article/details/50662592) ### 学习完本篇博客你能获得到的知识 1. 正确的获得view的大小 2. listview的下拉header 3. 自定义字体 4. 自己添加监听器 ## 废话不多说,快跟我来一起动手 首先来跟我打造一个自定义的listview 新建一个类,继承自listview,这里需要来重写一下他的overScrollBy()方法。 ~~~ public class HBListView extends ListView { //header显示的图片 private MyImageView mImageView; private Context mContext; //抢到红包时候的监听器 private OnSuccessListener mOnSuccessListener; @Override protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) { Log.e("wing", "deltaY:" + deltaY + " scrollY:" + scrollY); return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent); } ~~~ 先来介绍一下重要参数的意思 > deltaX,Y是指速度值,也就是你手指滑动的瞬时速度。  > scrollX,Y 水平和数值方向上的变化量  > isTouchEvent 表示手指是否在触摸状态 观察log可以轻易看到他们与手机触摸的关系。大家自己试验一下就可以。 接着给listview加一个header ~~~ mListView = (HBListView) findViewById(R.id.lv); final View header = LayoutInflater.from(this).inflate(R.layout.view_header,null); mListView.addHeaderView(header); ~~~ 其实这个header就是个图片。 我们刚才从listview的监听上面看到了偏移量。所以可以根据偏移量来动态改变图片的大小。 ~~~ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.wingsofts.hongbao.MyImageView android:id = "@+id/imageView" android:scaleType="centerCrop" android:layout_width="match_parent" android:layout_height="0dp" android:src="@drawable/bg" /> </LinearLayout> ~~~ 因为header刚开始是隐藏的,所以这里高度设置为0. 这里来一个小插曲。。不知道大家有没有在activity中获取view的高度呢。是不是有时候只能拿到0。对于这个问题呢。正确的获取view宽高有如下几种解决方案。 > 1.onWindowsFocusChanged()  > 2.view.postRunnable() 将一个runnable投递到消息队列尾部。  > 3.ViewTreeObserver 这里采用第三种方法,在OnGlobalLayoutListener中 调用listview的changSize方法。。 ~~~ header.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mListView.changeSize(image); header.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); ~~~ 这里看看listview的changsize方法,其实只是将header的引用传来而已..这是因为之前在这里获取高度,后来又去掉了。。偷懒没有改方法名 ~~~ public void changeSize(MyImageView imageView) { mImageView = imageView; } ~~~ 拿到了图片之后,理所当然是根据手指来改变图片的大小啦。 还记得onScrollBy里面的几个参数吗,我们只需要在 (触摸的时候&&下滑的时候) 改变图片的大小就可以了。  下滑的状态可以根据log得知为deltaY<0. ~~~ @Override protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) { Log.e("wing", "deltaY:" + deltaY + " scrollY:" + scrollY); //给图片一个最大值 if (mImageView.getHeight() < 300) { //是触摸状态 以及 是下滑状态 if (isTouchEvent && deltaY < 0) { //动态改变imageView的大小 mImageView.getLayoutParams().height += Math.abs(deltaY); mImageView.requestLayout(); } } return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent); } ~~~ 这样便可以下拉了,现在的效果是这样的。  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaeab8206c.jpg) 咦。。是可以下拉了。但是没有回弹,怎么办! 还能怎么办 写一个就是了。思路就是用一个动画,来不停地改变header高度。 ### ObjectAnimator的使用 他的使用方法很简单,他通过反射调用set方法来改变view的属性然后发生动画。这里直接上例子,首先重写一下imageview,添加一个setHeight()方法. ~~~ public class MyImageView extends ImageView { private Paint mPaint; public void setHeight(int height) { getLayoutParams().height = height; requestLayout(); } } ~~~ 然后在使用ObjectAnimator的ofInt方法,获取到一个ObjectAnimator,这里ofXXX具体看参数类型。 ~~~ //获取mImageView的setHeight方法,数值从当前图片的高度逐渐到0,产生动画 private void closeHeader() { ObjectAnimator oa = ObjectAnimator.ofInt(mImageView, "height", mImageView.getHeight(), 0); oa.start(); } ~~~ 这时候关闭动画就做完啦。想要关闭的时候 只需要在action_up调用closeHeader()方法即可.现在是这样的:  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaeabb73e5.jpg) ~~~ @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_UP: closeHeader(); break; } ~~~ 咦,有没有似曾相识的赶脚,没错!!这特么的不就是下拉刷新么。 好了本篇博客到此结束!! ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaeabe36ac.jpg) 额。。你先把手里的刀放下w(゚Д゚)w!!!  刚才结束的是下拉刷新的博客,现在继续写抢红包的博客。。。 思路就是用一个变量来保存连刷的次数,再用随机数判断是否抽中红包。  给MyImageView一个public的属性次数 ~~~ public class MyImageView extends ImageView { public int mTime; } ~~~ 然后只要在ACTION_UP的时候,判断随机数有没有抽中,决定次数是否累加: ~~~ @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_UP: int ran = (int) (Math.random() * 10); //设置中奖概率 if (ran > 1) { //如果没中次数累加 mImageView.mTime++; closeHeader(); } else { //否则为中奖 mImageView.mTime = 0; if (mOnSuccessListener != null) { mOnSuccessListener.onSuccess(); } closeHeader(); } break; } return super.onTouchEvent(ev); } ~~~ 逻辑是不是粉简单! 聪明的你已经看到了抢红包成功的回调。这个等等说,先说拿到了次数以后怎么做。 拿到了次数之后理所应道就是把文字画上去啦~~ 这里为了方便就给了一个固定的位置。因为数字大小跟文字不一样,所以把字符串分了三串来画.. ~~~ public int mTime; private Typeface mTypeFace; private float mTxtHLength; private float mTxtRLength; private String txtH = "连刷"; private String txtR = "次,加油!"; public MyImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mPaint = new Paint(); mPaint.setColor(Color.YELLOW); mMidPaint = new Paint(); mMidPaint.setColor(Color.YELLOW); mTypeFace = Typeface.createFromAsset(context.getAssets(), "ifont.ttf"); mMidPaint.setTypeface(mTypeFace); mPaint.setTypeface(mTypeFace); //设置文字大小 mPaint.setTextSize(50); //设置数字大小 mMidPaint.setTextSize(100); //测量头文字的长度 mTxtHLength = mPaint.measureText(txtH); //测量尾文字的长度 mTxtRLength = mPaint.measureText(txtR); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); String count = mTime + ""; //数字的长度 float countLength = mMidPaint.measureText(count); //总体长度 float totalLength = mTxtRLength + countLength; //画在正中间 float start = getMeasuredWidth() / 2 - totalLength / 2; canvas.drawText(txtH, 100, getMeasuredHeight() / 2, mPaint); canvas.drawText(count, start + countLength / 2, getMeasuredHeight() / 2, mMidPaint); canvas.drawText(txtR, start + countLength / 2 + mTxtRLength / 2, getMeasuredHeight() / 2, mPaint); } ~~~ 这样就把次数提示画上去了!来看看效果:  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaeac09be8.jpg)  这个…… 呃~~ -**__**-” 是画上去了,但是 ### 字!体!好!丑! 没事没事,别着急。。我们换一个字体就是了。 ### Paint字体的改变 > 1.首先下载一个字体 放到assets文件夹下  > 2.获取到字体的引用  > 3.给画笔设置字体 ~~~ //改变字体,就是这么轻松自如~~ mTypeFace = Typeface.createFromAsset(context.getAssets(), "ifont.ttf"); mMidPaint.setTypeface(mTypeFace); mPaint.setTypeface(mTypeFace); ~~~ 看效果图:  ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaeac26322.jpg) 吼吼吼~~~ 有点感觉了。 ### 监听器的添加 先来回过头来,大家知道View有setOnClickListener.. 那么我们也来加一个红包成功监听器吧。  在ListView里面增加一个内部接口,添加一个set方法 ~~~ private OnSuccessListener mOnSuccessListener; interface OnSuccessListener { void onSuccess(); } public void setOnSuccessListener(OnSuccessListener onSuccessListener) { mOnSuccessListener = onSuccessListener; } ~~~ 在ACTION_UP成果逻辑里面调用onSuccess()… 这就是监听器。。简单吧~就是个回调 现在在MainActivity里使用这个抢红包listView吧~  设置一个监听器,里面写你想要的事件,中几千万的随便写,总比气泡好,, ~~~ mListView.setAdapter(new ArrayAdapter(this,android.R.layout.simple_list_item_1,datas)); mListView.setOnSuccessListener(new HBListView.OnSuccessListener() { @Override public void onSuccess() { AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setMessage("恭喜中奖!抽到了疼逊聊天气泡!").setNegativeButton("确认", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }).show(); } }); ~~~ 这样就能出现文章顶部预览图的效果了: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-21_56efaeab10494.jpg) 如果你喜欢我的博客,请继续关注我,求赞求顶~求吐槽 本项目地址:[打开链接](https://github.com/githubwing/QQHongBao) ———————————–华丽丽的分割线———————————— ### 效果优化 处于追求完美的心态,群友 小情歌 对代码做了一些优化,主要有 > 1.修复上滑也显示红包的bug  > 2.优化动画事件,在松手的过程中如果继续ACTION_DOWN则放弃动画  > 3.增加视差特效 修改其实也不复杂 具体看这一段代码: ~~~ @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: x1 = ev.getX(); y1 = ev.getY(); if (oa != null) { if (oa.isRunning()) { oa.cancel(); } } if (mImageView.getHeight() == 0) { mImageView.mTime = 0; } break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: x2 = ev.getX(); y2 = ev.getY(); if (y1 - y2 > 0) { b = false; } else if (y2 - y1 > 0) { b = true; } int ran = (int) (Math.random() * 100); Log.e("wing", ran + ""); if (b) { //往下滑 if (ran > 3) { mImageView.mTime++; closeHeader(); } else { mImageView.mTime = 0; if (mOnSuccessListener != null) { mOnSuccessListener.onSuccess(); } closeHeader(); } } break; } return super.onTouchEvent(ev); } ~~~ 相信大家都看得懂,这里就不一一说明了
';