最近事情不是很多,在家无聊的时候看看直播,总能看到一些新奇的验证登录方式,正好自己最近也要去熟悉一下新的开发工具Android Studio,所以打算自己实现一下.

先看一下效果图:


一、确认需求

首先我们确认一下需求,我们分步来看
1.)需要定义一个阴影部分去遮盖图片,确定一个目标位置.
2.)需要一个带有边框的滑块内容,初始位置在最左边.
3.)阴影部分和滑块可以随机旋转,并保持一致.
4.)需要一个拖拽条使滑块随着拖拽条条拖拽而移动
5.)判断是否验证成功


二、分析问题

第一点,很简单我们只需要一直带有透明度的阴影图片,随机的去覆盖在图像上就可以实现.
第二点,需要用到画笔的setXfermode方法来设置图片叠加的显示模式.从而显示出一个带有边框的滑块.
第三点,需要改变图片的matrix来实现对图像的旋转.
第四点,安卓系统自带的SeekBar可以实现拖拽条的功能.
第五点,只需对外提供一个回调接口,来判断是否验证成功即可.


三、代码实现

首先创建一个attr文件来定义一些自定义属性,方便我们使用

<?xml version="1.0" encoding="utf-8"?>
<resources><!--滑块的高度--><attr name="unitHeight" format="dimension" /><!--滑块的宽度--><attr name="unitWidth" format="dimension" /><!--滑块占图片高度的比例--><attr name="unitHeightScale" format="integer" /><!--滑块占图片宽度的比例--><attr name="unitWidthScale" format="integer" /><!--滑块边框的图片资源--><attr name="unitShadeSrc" format="reference" /><!--阴影部分的图片资源--><attr name="unitShowSrc" format="reference" /><!--是否需要旋转--><attr name="needRotate" format="boolean" /><!--验证时的误差值--><attr name="deviate" format="integer" /><declare-styleable name="DouYuView"><attr name="unitHeight" /><attr name="unitWidth" /><attr name="unitHeightScale" /><attr name="unitWidthScale" /><attr name="unitShadeSrc" /><attr name="unitShowSrc" /><attr name="needRotate" /><attr name="deviate" /></declare-styleable>
</resources>

然后我们创建一个DouYuView继承自ImageView即可,来定义一下属性

    /*** 定义画笔*/private Paint mPaint;/*** 验证的图像*/private Bitmap mBitmap;/*** 验证滑块的高*/private int mUintHeight;/*** 验证滑块的宽*/private int mUintWidth;/*** 验证滑块宽占用整体图片大小的比例,默认1/5*/private int mUnitWidthScale;/*** 验证滑块高度占用整体图片大小的比例,默认1/4*/private int mUnitHeightScale;/*** 随机生成滑块的X坐标*/private int mUnitRandomX;/*** 随机生成滑块的Y坐标*/private int mUnitRandomY;/**** 滑块移动的距离*/private float mUnitMoveDistance = 0;/**** 滑块图像*/private Bitmap mUnitBp;/*** 验证位置图像*/private Bitmap mShowBp;/*** 背景阴影图像*/private Bitmap mShadeBp;/*** 是否需要旋转**/private boolean needRotate;/*** 旋转的角度*/private int rotate;/*** 判断是否完成的偏差量,默认为10*/public int DEFAULT_DEVIATE;/*** 判断是否重新绘制图像*/private boolean isReSet = true;

准备好这些之后,开始实现具体功能,这次我们打算用星星的图片,来设置阴影和滑块,首先我们准备两张图片,如下图(注意,带边框的图片中间部分,使用白色填充的):


为了使图片不会出现变形等异常,我们需要对图片进行获取及缩放的一些操作,代码如下:

    /*** 获取实际显示的图片** @return*/public Bitmap getBaseBitmap() {Bitmap b = drawableToBitamp(getDrawable());float scaleX = 1.0f;float scaleY = 1.0f;// 如果图片的宽或者高与view的宽高不匹配,计算出需要缩放的比例;缩放后的图片的宽高,一定要大于我们view的宽高;所以我们这里取大值;scaleX = getWidth() * 1.0f / b.getWidth();scaleY = getHeight() * 1.0f / b.getHeight();Matrix matrix = new Matrix();matrix.setScale(scaleX, scaleY);Bitmap bd = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(),matrix, true);return bd;}/*** drawable转bitmap** @param drawable* @return*/private Bitmap drawableToBitamp(Drawable drawable) {if (null == drawable) {return null;}if (drawable instanceof BitmapDrawable) {BitmapDrawable bd = (BitmapDrawable) drawable;return bd.getBitmap();}int w = drawable.getIntrinsicWidth();int h = drawable.getIntrinsicHeight();Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);drawable.setBounds(0, 0, w, h);drawable.draw(canvas);return bitmap;}/*** 缩放图片** @param bp* @param x* @param y* @return*/public static Bitmap handleBitmap(Bitmap bp, float x, float y) {int w = bp.getWidth();int h = bp.getHeight();float sx = (float) x / w;float sy = (float) y / h;Matrix matrix = new Matrix();matrix.postScale(sx, sy);Bitmap resizeBmp = Bitmap.createBitmap(bp, 0, 0, w,h, matrix, true);return resizeBmp;}

然后再去生成阴影图像和滑块

    /*** 创建遮挡的图片(阴影部分)** @return*/private Bitmap drawTargetBitmap() {// 绘制图片Bitmap showB;if (null != mShowBp) {showB = handleBitmap(mShowBp, mUintWidth, mUintHeight);} else {showB = handleBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.puzzle_show), mUintWidth, mUintHeight);}// 如果需要旋转图片,进行旋转,旋转后为了保持和滑块大小一致,需要重新缩放比例if (needRotate) {showB = handleBitmap(rotateBitmap(rotate, showB), mUintWidth, mUintHeight);}return showB;}/*** 创建结合的图片(滑块)** @param bp*/private Bitmap drawResultBitmap(Bitmap bp) {// 绘制图片Bitmap shadeB;if (null != mShadeBp) {shadeB = handleBitmap(mShadeBp, mUintWidth, mUintHeight);} else {shadeB = handleBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.puzzle_shade), mUintWidth, mUintHeight);}// 如果需要旋转图片,进行旋转,旋转后为了和画布大小保持一致,避免出现图像显示不全,需要重新缩放比例if (needRotate) {shadeB = handleBitmap(rotateBitmap(rotate, shadeB), mUintWidth, mUintHeight);}Bitmap resultBmp = Bitmap.createBitmap(mUintWidth, mUintHeight,Bitmap.Config.ARGB_8888);Paint paint = new Paint();paint.setAntiAlias(true);Canvas canvas = new Canvas(resultBmp);canvas.drawBitmap(shadeB, new Rect(0, 0, mUintWidth, mUintHeight),new Rect(0, 0, mUintWidth, mUintHeight), paint);// 选择交集去上层图片paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));canvas.drawBitmap(bp, new Rect(0, 0, mUintWidth, mUintHeight),new Rect(0, 0, mUintWidth, mUintHeight), paint);return resultBmp;}

准备好这些之后,还需要随机生成一个显示位置和对图像进行旋转的方法就可以在画布上去画出这些内容,代码如下:

    /*** 随机生成生成滑块的XY坐标*/private void initUnitXY() {mUnitRandomX = (int) (Math.random() * (mBitmap.getWidth() - mUintWidth));mUnitRandomY = (int) (Math.random() * (mBitmap.getHeight() - mUintHeight));// 防止生成的位置距离太近if (mUnitRandomX <= mBitmap.getWidth() / 2) {mUnitRandomX = mUnitRandomX + mBitmap.getWidth() / 4;}// 防止生成的X坐标截图时导致异常if (mUnitRandomX + mUintWidth > getWidth()) {initUnitXY();return;}}/*** 旋转图片** @param degree* @param bitmap* @return*/public Bitmap rotateBitmap(int degree, Bitmap bitmap) {Matrix matrix = new Matrix();matrix.postRotate(degree);Bitmap bm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),bitmap.getHeight(), matrix, true);return bm;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (isReSet) {mBitmap = getBaseBitmap();if (0 == mUintWidth) {mUintWidth = mBitmap.getWidth() / mUnitWidthScale;}if (0 == mUintHeight) {mUintHeight = mBitmap.getHeight() / mUnitHeightScale;}initUnitXY();mUnitBp = Bitmap.createBitmap(mBitmap, mUnitRandomX, mUnitRandomY, mUintWidth, mUintHeight);}isReSet = false;canvas.drawBitmap(drawTargetBitmap(), mUnitRandomX, mUnitRandomY, mPaint);canvas.drawBitmap(drawResultBitmap(mUnitBp), mUnitMoveDistance, mUnitRandomY, mPaint);}

我们还需要对控件进行重置、控制滑块移动、提供回调方法判断验证是否成功等方法.

    /*** 重置*/public void reSet() {isReSet = true;mUnitMoveDistance = 0;if (needRotate) {rotate = (int) (Math.random() * 3) * 90;} else {rotate = 0;}invalidate();}/*** 获取每次滑动的平均偏移值** @return*/public float getAverageDistance(int max) {return (float) (mBitmap.getWidth() - mUintWidth) / max;}/*** 滑块移动距离** @param distance*/public void setUnitMoveDistance(float distance) {mUnitMoveDistance = distance;// 防止滑块滑出图片if (mUnitMoveDistance > mBitmap.getWidth() - mUintWidth) {mUnitMoveDistance = mBitmap.getWidth() - mUintWidth;}invalidate();}/*** 拼图成功的回调**/interface onPuzzleListener {public void onSuccess();public void onFail();}/*** 回调*/private onPuzzleListener mlistener;/*** 设置回调** @param listener*/public void setPuzzleListener(onPuzzleListener listener) {this.mlistener = listener;}/*** 验证是否拼接成功*/public void testPuzzle() {if (Math.abs(mUnitMoveDistance - mUnitRandomX) <= DEFAULT_DEVIATE) {if (null != mlistener) {mlistener.onSuccess();}} else {if (null != mlistener) {mlistener.onFail();}}}

基本上我们需要的功能就实现了.


四、完整的代码及使用

DouYuView:

package com.example.junweiliu.douyutest;import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;/*** Created by junweiliu on 16/4/26.*/
public class DouYuView extends ImageView {/*** 定义画笔*/private Paint mPaint;/*** 验证的图像*/private Bitmap mBitmap;/*** 验证滑块的高*/private int mUintHeight;/*** 验证滑块的宽*/private int mUintWidth;/*** 验证滑块宽占用整体图片大小的比例,默认1/5*/private int mUnitWidthScale;/*** 验证滑块高度占用整体图片大小的比例,默认1/4*/private int mUnitHeightScale;/*** 随机生成滑块的X坐标*/private int mUnitRandomX;/*** 随机生成滑块的Y坐标*/private int mUnitRandomY;/**** 滑块移动的距离*/private float mUnitMoveDistance = 0;/**** 滑块图像*/private Bitmap mUnitBp;/*** 验证位置图像*/private Bitmap mShowBp;/*** 背景阴影图像*/private Bitmap mShadeBp;/*** 是否需要旋转**/private boolean needRotate;/*** 旋转的角度*/private int rotate;/*** 判断是否完成的偏差量,默认为10*/public int DEFAULT_DEVIATE;/*** 判断是否重新绘制图像*/private boolean isReSet = true;/*** 拼图成功的回调**/interface onPuzzleListener {public void onSuccess();public void onFail();}/*** 回调*/private onPuzzleListener mlistener;/*** 设置回调** @param listener*/public void setPuzzleListener(onPuzzleListener listener) {this.mlistener = listener;}public DouYuView(Context context) {this(context, null);}public DouYuView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public DouYuView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.DouYuView);mUintWidth = ta.getDimensionPixelOffset(R.styleable.DouYuView_unitHeight, 0);mUintHeight = ta.getDimensionPixelOffset(R.styleable.DouYuView_unitHeight, 0);mUnitHeightScale = ta.getInteger(R.styleable.DouYuView_unitHeightScale, 4);mUnitWidthScale = ta.getInteger(R.styleable.DouYuView_unitWidthScale, 5);Drawable showBp = ta.getDrawable(R.styleable.DouYuView_unitShowSrc);mShowBp = drawableToBitamp(showBp);Drawable shadeBp = ta.getDrawable(R.styleable.DouYuView_unitShadeSrc);mShadeBp = drawableToBitamp(shadeBp);needRotate = ta.getBoolean(R.styleable.DouYuView_needRotate, true);DEFAULT_DEVIATE = ta.getInteger(R.styleable.DouYuView_deviate, 10);ta.recycle();// 初始化mPaint = new Paint();mPaint.setAntiAlias(true);if (needRotate) {rotate = (int) (Math.random() * 3) * 90;} else {rotate = 0;}}/*** 随机生成生成滑块的XY坐标*/private void initUnitXY() {mUnitRandomX = (int) (Math.random() * (mBitmap.getWidth() - mUintWidth));mUnitRandomY = (int) (Math.random() * (mBitmap.getHeight() - mUintHeight));// 防止生成的位置距离太近if (mUnitRandomX <= mBitmap.getWidth() / 2) {mUnitRandomX = mUnitRandomX + mBitmap.getWidth() / 4;}// 防止生成的X坐标截图时导致异常if (mUnitRandomX + mUintWidth > getWidth()) {initUnitXY();return;}}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (isReSet) {mBitmap = getBaseBitmap();if (0 == mUintWidth) {mUintWidth = mBitmap.getWidth() / mUnitWidthScale;}if (0 == mUintHeight) {mUintHeight = mBitmap.getHeight() / mUnitHeightScale;}initUnitXY();mUnitBp = Bitmap.createBitmap(mBitmap, mUnitRandomX, mUnitRandomY, mUintWidth, mUintHeight);}isReSet = false;canvas.drawBitmap(drawTargetBitmap(), mUnitRandomX, mUnitRandomY, mPaint);canvas.drawBitmap(drawResultBitmap(mUnitBp), mUnitMoveDistance, mUnitRandomY, mPaint);}/*** 重置*/public void reSet() {isReSet = true;mUnitMoveDistance = 0;if (needRotate) {rotate = (int) (Math.random() * 3) * 90;} else {rotate = 0;}invalidate();}/*** 获取每次滑动的平均偏移值** @return*/public float getAverageDistance(int max) {return (float) (mBitmap.getWidth() - mUintWidth) / max;}/*** 滑块移动距离** @param distance*/public void setUnitMoveDistance(float distance) {mUnitMoveDistance = distance;// 防止滑块滑出图片if (mUnitMoveDistance > mBitmap.getWidth() - mUintWidth) {mUnitMoveDistance = mBitmap.getWidth() - mUintWidth;}invalidate();}/*** 验证是否拼接成功*/public void testPuzzle() {if (Math.abs(mUnitMoveDistance - mUnitRandomX) <= DEFAULT_DEVIATE) {if (null != mlistener) {mlistener.onSuccess();}} else {if (null != mlistener) {mlistener.onFail();}}}/*** 创建遮挡的图片(阴影部分)** @return*/private Bitmap drawTargetBitmap() {// 绘制图片Bitmap showB;if (null != mShowBp) {showB = handleBitmap(mShowBp, mUintWidth, mUintHeight);} else {showB = handleBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.puzzle_show), mUintWidth, mUintHeight);}// 如果需要旋转图片,进行旋转,旋转后为了保持和滑块大小一致,需要重新缩放比例if (needRotate) {showB = handleBitmap(rotateBitmap(rotate, showB), mUintWidth, mUintHeight);}return showB;}/*** 创建结合的图片(滑块)** @param bp*/private Bitmap drawResultBitmap(Bitmap bp) {// 绘制图片Bitmap shadeB;if (null != mShadeBp) {shadeB = handleBitmap(mShadeBp, mUintWidth, mUintHeight);} else {shadeB = handleBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.puzzle_shade), mUintWidth, mUintHeight);}// 如果需要旋转图片,进行旋转,旋转后为了和画布大小保持一致,避免出现图像显示不全,需要重新缩放比例if (needRotate) {shadeB = handleBitmap(rotateBitmap(rotate, shadeB), mUintWidth, mUintHeight);}Bitmap resultBmp = Bitmap.createBitmap(mUintWidth, mUintHeight,Bitmap.Config.ARGB_8888);Paint paint = new Paint();paint.setAntiAlias(true);Canvas canvas = new Canvas(resultBmp);canvas.drawBitmap(shadeB, new Rect(0, 0, mUintWidth, mUintHeight),new Rect(0, 0, mUintWidth, mUintHeight), paint);// 选择交集去上层图片paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));canvas.drawBitmap(bp, new Rect(0, 0, mUintWidth, mUintHeight),new Rect(0, 0, mUintWidth, mUintHeight), paint);return resultBmp;}/*** 获取实际显示的图片** @return*/public Bitmap getBaseBitmap() {Bitmap b = drawableToBitamp(getDrawable());float scaleX = 1.0f;float scaleY = 1.0f;// 如果图片的宽或者高与view的宽高不匹配,计算出需要缩放的比例;缩放后的图片的宽高,一定要大于我们view的宽高;所以我们这里取大值;scaleX = getWidth() * 1.0f / b.getWidth();scaleY = getHeight() * 1.0f / b.getHeight();Matrix matrix = new Matrix();matrix.setScale(scaleX, scaleY);Bitmap bd = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(),matrix, true);return bd;}/*** drawable转bitmap** @param drawable* @return*/private Bitmap drawableToBitamp(Drawable drawable) {if (null == drawable) {return null;}if (drawable instanceof BitmapDrawable) {BitmapDrawable bd = (BitmapDrawable) drawable;return bd.getBitmap();}int w = drawable.getIntrinsicWidth();int h = drawable.getIntrinsicHeight();Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);drawable.setBounds(0, 0, w, h);drawable.draw(canvas);return bitmap;}/*** 缩放图片** @param bp* @param x* @param y* @return*/public static Bitmap handleBitmap(Bitmap bp, float x, float y) {int w = bp.getWidth();int h = bp.getHeight();float sx = (float) x / w;float sy = (float) y / h;Matrix matrix = new Matrix();matrix.postScale(sx, sy);Bitmap resizeBmp = Bitmap.createBitmap(bp, 0, 0, w,h, matrix, true);return resizeBmp;}/*** 旋转图片** @param degree* @param bitmap* @return*/public Bitmap rotateBitmap(int degree, Bitmap bitmap) {Matrix matrix = new Matrix();matrix.postRotate(degree);Bitmap bm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),bitmap.getHeight(), matrix, true);return bm;}
}

MainActivity:

package com.example.junweiliu.douyutest;import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.SeekBar;
import android.widget.Toast;public class MainActivity extends Activity {/*** 滑块*/private SeekBar mSeekBar;/*** 自定义的控件*/private DouYuView mDY;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}private void initView() {mDY = (DouYuView) findViewById(R.id.dy_v);mSeekBar = (SeekBar) findViewById(R.id.sb_dy);mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onProgressChanged(SeekBar seekBar, int i, boolean b) {
//                Log.e("main", "当前位置" + i);mDY.setUnitMoveDistance(mDY.getAverageDistance(seekBar.getMax()) * i);}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {mDY.testPuzzle();}});mDY.setPuzzleListener(new DouYuView.onPuzzleListener() {@Overridepublic void onSuccess() {
//                mSeekBar.setEnabled(false);Toast.makeText(MainActivity.this, "验证成功", Toast.LENGTH_SHORT).show();mSeekBar.setProgress(0);mDY.reSet();}@Overridepublic void onFail() {Toast.makeText(MainActivity.this, "验证失败", Toast.LENGTH_SHORT).show();mSeekBar.setProgress(0);}});}
}

xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:dy="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context="com.example.junweiliu.douyutest.MainActivity"><com.example.junweiliu.douyutest.DouYuViewandroid:id="@+id/dy_v"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginBottom="10dp"android:src="@mipmap/test"dy:unitHeight="60dp"dy:unitWidth="80dp"dy:unitShowSrc="@mipmap/star_show"dy:unitShadeSrc="@mipmap/star_shade"dy:needRotate="true" /><SeekBarandroid:id="@+id/sb_dy"android:layout_width="match_parent"android:layout_height="wrap_content"android:max="100" />
</LinearLayout>

实现的效果图如下:

下载地址

Android仿斗鱼滑动登录验证相关推荐

  1. Android仿斗鱼领取鱼丸文字验证(三)

    今天来写最后一部分,九宫格部分,先来看一下最终的效果图: 一.分析功能 直接看下边的九宫格,九宫格里边的文字随机,文字颜色随机,并且每个文字都进行了不同程度的扭曲变形,点击看不清的时候,可以对九宫格的 ...

  2. php仿微信底部菜单,Android实现简单底部导航栏 Android仿微信滑动切换效果

    Android仿微信滑动切换最终实现效果: 大体思路: 1. 主要使用两个自定义View配合实现; 底部图标加文字为一个自定义view,底部导航栏为一个载体,根据需要来添加底部图标; 2. 底部导航栏 ...

  3. android仿微信的activity平滑水平切换动画,Android实现简单底部导航栏 Android仿微信滑动切换效果...

    Android实现简单底部导航栏 Android仿微信滑动切换效果 发布时间:2020-10-09 19:48:00 来源:脚本之家 阅读:96 作者:丶白泽 Android仿微信滑动切换最终实现效果 ...

  4. android滑动菜单图标,Android实现简单底部导航栏 Android仿微信滑动切换效果

    Android仿微信滑动切换最终实现效果: 大体思路: 1. 主要使用两个自定义View配合实现; 底部图标加文字为一个自定义view,底部导航栏为一个载体,根据需要来添加底部图标; 2. 底部导航栏 ...

  5. android 底部滑动效果怎么做,Android实现简单底部导航栏 Android仿微信滑动切换效果...

    android仿微信滑动切换最终实现效果: 大体思路: 1. 主要使用两个自定义view配合实现; 底部图标加文字为一个自定义view,底部导航栏为一个载体,根据需要来添加底部图标; 2. 底部导航栏 ...

  6. android 仿微信demo————登录功能实现(服务端)

    android 仿微信demo----微信启动界面实现 android 仿微信demo----注册功能实现(移动端) android 仿微信demo----注册功能实现(服务端) android 仿微 ...

  7. android 仿微信demo————登录功能实现(移动端)

    android 仿微信demo----微信启动界面实现 android 仿微信demo----注册功能实现(移动端) android 仿微信demo----注册功能实现(服务端) android 仿微 ...

  8. Android仿IOS滑动关机-自定义view系列(6)

    Android仿IOS滑动关机-自定义view系列 功能简介 GIf演示 主要实现步骤-具体内容看github项目里的代码 Android技术生活交流 更多其他页面-自定义View-实用功能合集:点击 ...

  9. Android 仿斗鱼、映客 礼物打赏,包括连击、追加等功能

    Android 仿斗鱼.映客 礼物打赏  公司之前需要对直播功能添加打赏模块,我把自己的方法以及参考的demo分享出来. 参考demo:https://github.com/Qiang3570/Liv ...

最新文章

  1. oracle 错误解决
  2. How to be a great Project Director
  3. SDE+ORACLE优化配置
  4. Wine 4.3 发布,Windows 应用的兼容层
  5. linux中_在 Linux 桌面中开始使用 Lumina | Linux 中国
  6. 发些c/c++/vc/驱动/网络安全的好书和资料
  7. 收藏 | 各种Optimizer梯度下降优化算法回顾和总结
  8. elasticsearch新增_SpringBoot 使用JestClient操作Elasticsearch
  9. MySQL数据库与Oracle数据库中建表使用单引号和双引号的效果
  10. 娇小可人女友9号 4K无反相机松下GF9评测
  11. 电脑装机人员、管理软件安装实施人员必备工具包使用教程汇总值得收藏
  12. NFC读卡MS522|CV520
  13. h5画三角形_HTML5怎么画三角形?
  14. 龙芯电脑的详细资料,支持国货的请进来!
  15. 显存测试软件linux环境_CI/CD 中的自动化测试的概要知识 | Linux 中国
  16. 度过漫长又艰辛的 2020 年,我收集了 1273 人的年度感悟
  17. 我的世界服务器清垃圾文件,我的世界:五大处理“垃圾”方法,我选择懒人方法,你会如何选?...
  18. 微信图文编辑器如何添加超链接?
  19. python最小二乘法 实现 曲面拟合
  20. 利盟linux驱动下载,利盟Lexmark X342n 驱动下载

热门文章

  1. vue生成pdf文件
  2. E5 2670 V1 C1 C2版本区别
  3. 洗地机那个牌子好?洗地机品牌排行榜
  4. Mac电脑用预览功能调整图像大小?Mac调整图片大小方法
  5. stl库的使用——队列queue和优先队列和优先队列小根堆(全家桶哎)
  6. Windows日志查看工具分享
  7. 对比学习论文综述(part4 transformer + 总结)
  8. mysql:innodb存储引擎之表结构
  9. 在线上也能进行商标注册
  10. Linux的起源:从一个故事说起