晨鸣的博客–神奇的水滴效果导航栏-BezierIndicator

很早之前就看见过这样一个特效

心怡很久,却一直恐于自定义View这座大山。最近在突击自定义View的技能,学习贝塞尔曲线的绘制,前面搞了个很简单的MagicButton,甚是兴奋�� 所以斗胆来试试看实现这个特效。

分析

找了半天终于找到当初看见的这个特效的原博客 –三次贝塞尔曲线练习之弹性的圆

另外在评论中发现竟然有人已经实现了这个自定义View了–自定义View之炫酷的水滴ViewPageIndicator,效果很不错,借鉴之��

关于最核心的贝塞尔小球动效的绘制,博主进行了很详细的解析及描述,并且提供了一个demo,万分感谢��

这里简单回顾一下这个小球的绘制过程:

为了控制小球的不同形态,我们这里使用三阶贝塞尔曲线cubicTo来绘制小球。

而小球一共可以分成5个状态来绘制

状态1

状态2

状态3

状态4

状态5

绘制

计算控件宽高

作为一个导航控件,我暂时不考虑宽度设置为warp_content的状态,设置wrap_content一律计算为屏幕的最大宽高.

    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);/*** 获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式*/int widthMode = MeasureSpec.getMode(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);if (widthMode == MeasureSpec.EXACTLY) {width = sizeWidth;} else {width = wm.getDefaultDisplay().getWidth();}if (heightMode == MeasureSpec.EXACTLY) {height = sizeHeight;} else {height = wm.getDefaultDisplay().getHeight();}if (getChildCount() != 0) {childSideLength = (width - getPaddingRight() - getPaddingLeft()) / getChildCount() > height - getPaddingBottom() - getPaddingTop() ? height - getPaddingBottom() - getPaddingTop() : (width - getPaddingLeft() - getPaddingRight()) / getChildCount();
//        //计算出所有的ChildView的宽和高
//            measureChildren(widthMeasureSpec, heightMeasureSpec);bezierCircular = new BezierCircular(childSideLength / 2);}setMeasuredDimension(width, height);}

计算子控件的位置

为了方便管理,子View的大小统一计算为一个正方形区域,设置一个子View的padding值childPadding,可以通过childPadding值控制我们添加的子view呈现出的大小,也就是效果图中小图标在白色圆环中的大小。

 @Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {int childCount = getChildCount();if (childCount == 0) {return;}//相邻两个子View中心点的间距float childDis = (width - getPaddingLeft() - getPaddingRight() - 2 * defaultLeftRightGap - childSideLength) / (childCount - 1);float cWidth = childSideLength - 2 * childPadding;float cHeight = cWidth;anchorList.clear();//计算子控件的位置,强制将子View控制绘制在均分的几个锚点上for (int i = 0; i < childCount; i++) {View childView = getChildAt(i);PointF anchorPoint = new PointF((childDis * i + defaultLeftRightGap + childSideLength / 2 + getPaddingLeft()), getPaddingTop() + childSideLength / 2);anchorList.add(anchorPoint);childView.layout((int) (anchorPoint.x - cWidth / 2), (int) (anchorPoint.y - cHeight / 2), (int) (anchorPoint.x + cWidth / 2), (int) (anchorPoint.y + cHeight / 2));}PointF pointF = anchorList.get(0);bezierCircular.setCenter(pointF.x, pointF.y);bezierCircular.initControlPoint();}

绘制贝塞尔小球

将贝塞尔小球的一些参数及计算封装成一个对象BezierCircular,因为刚开始只是看了原博客的思路就动手了,绘制贝塞尔小球使用了最原始的方法,定义了4个数据点和8个控制点,在进行五个状态的绘制计算的时候太麻烦了,后面看了博客中的Demo,发现自己的计算太原始笨重了,博客中的demo中关于小球的绘制更加面向对象,更加简洁。不过既然是原创,还是要贴出自己的代码,仅供参考��

public class BezierCircular {private static final String TAG = "BezierCircular";private static final float C = 0.551915024494f; //常量//圆中心坐标float centerX;float centerY;//圆半径float radius;private PointF currentPoint;private PointF targetPoint;private float mDifference;private float stretchDistance;private float cDistance;private float moveDistance;private float[] mData = new float[8];  //顺时针记录绘制圆形的四个数据点private float[] mCtrl = new float[16];  //顺时针记录绘制圆形的八个控制点public BezierCircular(float radius) {this.radius = radius;stretchDistance = radius / 3 * 2;mDifference = radius * C;cDistance = mDifference * 0.45f;}public void setCenter(float centerX, float centerY) {this.centerX = centerX;this.centerY = centerY;}public void initControlPoint() {//初始化数据点mData[0] = centerX;mData[1] = centerY + radius;mData[2] = centerX + radius;mData[3] = centerY;mData[4] = centerX;mData[5] = centerY - radius;mData[6] = centerX - radius;mData[7] = centerY;//初始化控制点mCtrl[0] = mData[0] + mDifference;mCtrl[1] = mData[1];mCtrl[2] = mData[2];mCtrl[3] = mData[3] + mDifference;mCtrl[4] = mData[2];mCtrl[5] = mData[3] - mDifference;mCtrl[6] = mData[4] + mDifference;mCtrl[7] = mData[5];mCtrl[8] = mData[4] - mDifference;mCtrl[9] = mData[5];mCtrl[10] = mData[6];mCtrl[11] = mData[7] - mDifference;mCtrl[12] = mData[6];mCtrl[13] = mData[7] + mDifference;mCtrl[14] = mData[0] - mDifference;mCtrl[15] = mData[1];}public void setCurrentAndTarget(PointF currentPoint, PointF targetPoint) {this.currentPoint = currentPoint;this.targetPoint = targetPoint;float distance = targetPoint.x - currentPoint.x;moveDistance = distance > 0 ? distance - 2 * stretchDistance : distance + 2 * stretchDistance;}public void setProgress(float progress) {if ((progress > 0 && progress <= 0.2) || (progress < 0 && progress >= -0.2)) {model1(progress);} else if ((progress > 0.2 && progress <= 0.5) || (progress < -0.2 && progress >= -0.5)) {model2(progress);} else if ((progress > 0.5 && progress <= 0.8) || (progress < -0.5 && progress >= -0.8)) {model3(progress);} else if ((progress > 0.8 && progress <= 0.9) || (progress < -0.8 && progress >= -0.9)) {model4(progress);} else if ((progress > 0.9 && progress < 1) || (progress < -0.9 && progress > -1)) {model5(progress);}
//        } else if (progress >= 1 || progress <= -1) {//            Log.i(TAG,"-------------------------------------------");
////            centerX = targetPoint.x;
////            centerY = targetPoint.y;
////            initControlPoint();
//        }}public void model1(float progress) {if (progress > 0)mData[2] = centerX + radius + stretchDistance * progress * 5;if (progress < 0)mData[6] = centerX - radius + stretchDistance * progress * 5;mCtrl[2] = mData[2];if (progress > 0)mCtrl[3] = mData[3] + mDifference + cDistance * progress * 5;mCtrl[4] = mData[2];if (progress > 0)mCtrl[5] = mData[3] - mDifference - cDistance * progress * 5;mCtrl[10] = mData[6];if (progress < 0)mCtrl[11] = mData[7] - mDifference + cDistance * progress * 5;mCtrl[12] = mData[6];if (progress < 0)mCtrl[13] = mData[7] + mDifference - cDistance * progress * 5;}public void model2(float progress) {model1(progress > 0 ? 0.2f : -0.2f);progress = progress > 0 ? (progress - 0.2f) * (10f / 3) : (progress + 0.2f) * (10f / 3);//初始化数据点mData[0] = centerX + stretchDistance * progress;if (progress > 0)mData[2] = centerX + radius + stretchDistance * (1 + progress);elsemData[2] = centerX + radius;mData[4] = centerX + stretchDistance * progress;if (progress < 0)mData[6] = centerX - radius - stretchDistance + stretchDistance * progress;elsemData[6] = centerX - radius;//初始化控制点mCtrl[0] = mData[0] + mDifference;mCtrl[2] = mData[2];if (progress > 0)mCtrl[3] = mData[3] + mDifference + cDistance;elsemCtrl[3] = mData[3] + mDifference - cDistance * progress;mCtrl[4] = mData[2];if (progress > 0)mCtrl[5] = mData[3] - mDifference - cDistance;elsemCtrl[5] = mData[3] - mDifference + cDistance * progress;mCtrl[6] = mData[4] + mDifference;mCtrl[8] = mData[4] - mDifference;mCtrl[10] = mData[6];if (progress > 0)mCtrl[11] = mData[7] - mDifference - cDistance * progress;elsemCtrl[11] = mData[7] - mDifference - cDistance;mCtrl[12] = mData[6];if (progress > 0)mCtrl[13] = mData[7] + mDifference + cDistance * progress;elsemCtrl[13] = mData[7] + mDifference + cDistance;mCtrl[14] = mData[0] - mDifference;}public void model3(float progress) {model2(progress > 0 ? 0.5f : -0.5f);progress = progress > 0 ? (progress - 0.5f) * (10f / 3) : (progress + 0.5f) * (10f / 3);//初始化数据点if (progress > 0)mData[0] = centerX + moveDistance * progress + stretchDistance;elsemData[0] = centerX - moveDistance * progress - stretchDistance;if (progress > 0)mData[2] = centerX + moveDistance * progress + radius + 2 * stretchDistance;elsemData[2] = centerX - moveDistance * progress + radius;if (progress > 0)mData[4] = centerX + moveDistance * progress + stretchDistance;elsemData[4] = centerX - moveDistance * progress - stretchDistance;if (progress > 0)mData[6] = centerX + moveDistance * progress - radius;elsemData[6] = centerX - moveDistance * progress - radius - 2 * stretchDistance;//初始化控制点mCtrl[0] = mData[0] + mDifference;mCtrl[2] = mData[2];mCtrl[3] = mData[3] + mDifference + cDistance;mCtrl[4] = mData[2];mCtrl[5] = mData[3] - mDifference - cDistance;mCtrl[6] = mData[4] + mDifference;mCtrl[8] = mData[4] - mDifference;mCtrl[10] = mData[6];mCtrl[11] = mData[7] - mDifference - cDistance;mCtrl[12] = mData[6];mCtrl[13] = mData[7] + mDifference + cDistance;mCtrl[14] = mData[0] - mDifference;}public void model4(float progress) {model3(progress > 0 ? 0.8f : -0.8f);progress = progress > 0 ? (progress - 0.8f) * 10 : (progress + 0.8f) * 10;//初始化数据点if (progress > 0)mData[0] = centerX + moveDistance + stretchDistance + stretchDistance * progress;elsemData[0] = centerX + moveDistance - stretchDistance + stretchDistance * progress;if (progress > 0)mData[2] = centerX + moveDistance + radius + 2 * stretchDistance;elsemData[2] = centerX + moveDistance + radius + stretchDistance * progress;if (progress > 0)mData[4] = centerX + moveDistance + stretchDistance + stretchDistance * progress;elsemData[4] = centerX + moveDistance - stretchDistance + stretchDistance * progress;if (progress > 0)mData[6] = centerX + moveDistance - radius + stretchDistance * progress;elsemData[6] = centerX + moveDistance - radius - 2 * stretchDistance;//初始化控制点mCtrl[0] = mData[0] + mDifference;mCtrl[2] = mData[2];if (progress > 0)mCtrl[3] = mData[3] + mDifference + cDistance - cDistance * progress;elsemCtrl[3] = mData[3] + mDifference + cDistance;mCtrl[4] = mData[2];if (progress > 0)mCtrl[5] = mData[3] - mDifference - cDistance + cDistance * progress;elsemCtrl[5] = mData[3] - mDifference - cDistance;mCtrl[6] = mData[4] + mDifference;mCtrl[8] = mData[4] - mDifference;mCtrl[10] = mData[6];if (progress > 0)mCtrl[11] = mData[7] - mDifference - cDistance;elsemCtrl[11] = mData[7] - mDifference - cDistance - cDistance * progress;mCtrl[12] = mData[6];if (progress > 0)mCtrl[13] = mData[7] + mDifference + cDistance;elsemCtrl[13] = mData[7] + mDifference + cDistance + cDistance * progress;mCtrl[14] = mData[0] - mDifference;}public void model5(float progress) {model4(progress > 0 ? 0.9f : -0.9f);progress = progress > 0 ? (progress - 0.9f) * 10 : (progress + 0.9f) * 10;//初始化数据点if (progress > 0)mData[0] = centerX + moveDistance + 2 * stretchDistance;elsemData[0] = centerX + moveDistance - 2 * stretchDistance;if (progress > 0)mData[2] = centerX + moveDistance + radius + 2 * stretchDistance;elsemData[2] = (float) (centerX + moveDistance + radius - stretchDistance - (Math.sin(Math.PI * 3 / 2 * Math.abs(progress) - Math.PI / 2) + 1) * stretchDistance);if (progress > 0)mData[4] = centerX + moveDistance + 2 * stretchDistance;elsemData[4] = centerX + moveDistance - 2 * stretchDistance;if (progress > 0)mData[6] = (float) (centerX + moveDistance - radius + stretchDistance + (Math.sin(Math.PI * 3 / 2 * progress - Math.PI / 2) + 1) * stretchDistance);elsemData[6] = centerX + moveDistance - radius - 2 * stretchDistance;//初始化控制点mCtrl[0] = mData[0] + mDifference;mCtrl[2] = mData[2];if (progress < 0)mCtrl[3] = mData[3] + mDifference + cDistance + cDistance * progress;mCtrl[4] = mData[2];if (progress < 0)mCtrl[5] = mData[3] - mDifference - cDistance - cDistance * progress;mCtrl[6] = mData[4] + mDifference;mCtrl[8] = mData[4] - mDifference;mCtrl[10] = mData[6];if (progress > 0)mCtrl[11] = mData[7] - mDifference - cDistance + cDistance * progress;mCtrl[12] = mData[6];if (progress > 0)mCtrl[13] = mData[7] + mDifference + cDistance - cDistance * progress;mCtrl[14] = mData[0] - mDifference;}public void drawCircle(Canvas canvas, Paint mPaint) {Path path = new Path();path.moveTo(mData[0], mData[1]);path.cubicTo(mCtrl[0], mCtrl[1], mCtrl[2], mCtrl[3], mData[2], mData[3]);path.cubicTo(mCtrl[4], mCtrl[5], mCtrl[6], mCtrl[7], mData[4], mData[5]);path.cubicTo(mCtrl[8], mCtrl[9], mCtrl[10], mCtrl[11], mData[6], mData[7]);path.cubicTo(mCtrl[12], mCtrl[13], mCtrl[14], mCtrl[15], mData[0], mData[1]);canvas.drawPath(path, mPaint);}public void resetCircular(PointF pointF) {setCenter(pointF.x, pointF.y);initControlPoint();}}

确定子View点击位置

通过OnTouchEvent 方法计算触摸点在哪个子View的绘制范围内,确定点击位置

    float touchX = 0;float touchY = 0;@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:touchX = event.getX();touchY = event.getY();break;case MotionEvent.ACTION_UP:Log.i(TAG, "touchX: " + touchX + "  touchY: " + touchY);for (int i = 0; i < anchorList.size(); i++) {PointF pointF = anchorList.get(i);if (touchX > (pointF.x - childSideLength / 2) && touchX < (pointF.x + childSideLength / 2) && touchY > (pointF.y - childSideLength / 2) && touchY < (pointF.y + childSideLength / 2)) {onClickIndex(i);}}break;}return true;}private void onClickIndex(int position) {if (!isAnimatorStart && !isViewPagerScoll && position != currentPosition) {targetPosition = position;isAnimatorStart = true;startAnimator(); //开始动画clickAnimator(); //点击效果if (viewPager != null) {viewPager.setCurrentItem(position);}
//            currentPosition = position;Log.i(TAG, "点击了第 " + position + " 项!");}}

点击切换动画

通过ValueAnimator动态更改贝塞尔小球的绘制进度

  /*** 切换动画*/private void startAnimator() {bezierCircular.setCurrentAndTarget(anchorList.get(currentPosition), anchorList.get(targetPosition));ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, targetPosition > currentPosition ? 1 : -1);valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {bezierCircular.setProgress((Float) animation.getAnimatedValue());bezierPaint.setColor(circularColors.size() > 0 ? setCircularColor(Math.abs((Float) animation.getAnimatedValue())) : circularColor);postInvalidate();}});valueAnimator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {currentPosition = targetPosition;bezierPaint.setColor(circularColors.size() > 0 ? circularColors.get(currentPosition) : circularColor);bezierCircular.resetCircular(anchorList.get(currentPosition));isAnimatorStart = false;postInvalidate();super.onAnimationEnd(animation);}});int count = Math.abs(targetPosition - currentPosition);if (count == 0) {return;}int duration = 600;valueAnimator.setDuration(duration);valueAnimator.start();}

与ViewPager联动

与ViewPager的联动这一块挺头疼的,ViewPager滚动过程中设置滑动监听 void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) 回调方法中的 positionOffset 参数,在从左往右滑是0~1逐渐增大,但是最后又会突变到0。而且 void onPageSelected(int position)回调方法并不是在ViewPager滑动结束的时候调用,而是在你的手指离开时调用,有可能ViewPager还在惯性滑动的时候void onPageSelected(int position)方法已经调用了,所以也没办法通过这个回调来确定 currentPositontargetPosition

通过观察,ViewPager的滑动监听 void onPageScrollStateChanged(int state)回调方法中有三个状态

  1. state == 1 表示正在滑动
  2. state == 2 表示滑动结束
  3. state == 0 表示什么都没有做

这里的滑动指的是手指在屏幕上的滑动,而当ViewPager惯性滑动结束时 state == 0,所以最后决定在void onPageScrollStateChanged(int state)方法中进行相关处理。

 public void setViewPager(ViewPager viewPager) {this.viewPager = viewPager;viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {if (anchorList != null && anchorList.size() > 0 && !isAnimatorStart) {isViewPagerScoll = true;updateDrop(position, positionOffset, positionOffsetPixels);}// 页面正在滚动时不断调用Log.d(TAG, "onPageScrolled————>" + "    position:" + position + "    positionOffest:" + positionOffset + "    positionOffsetPixels:" + positionOffsetPixels);}@Overridepublic void onPageSelected(int position) {Log.e(TAG, "onPagerSelected————>    position:" + position);isSelected = true;}@Overridepublic void onPageScrollStateChanged(int state) {if (state == 0 && isSelected && !isAnimatorStart) {
//                    Log.e(TAG, "onPageScrollStateChanged————>    设置状态:");isSelected = false;isViewPagerScoll = false;bezierCircular.setProgress(direction ? 1.0f : -1.0f);currentPosition = targetPosition;//                    Log.i(TAG, "currentPosition::::" + currentPosition);bezierPaint.setColor(circularColors.size() > 0 ? circularColors.get(currentPosition) : circularColor);bezierCircular.resetCircular(anchorList.get(currentPosition));postInvalidate();}Log.i(TAG, "onPageScrollStateChanged————>    state:" + state);}});}float lastProgress = 0;float currentProgress = 0;//滑动ViewPager时更新指示器的动画private void updateDrop(int position, float positionOffset, int positionOffsetPixels) {if ((position + positionOffset) - currentPosition > 0) {direction = true;} else if ((position + positionOffset) - currentPosition < 0) {direction = false;}//防止数组越界if ((!direction && currentPosition - 1 < 0) || (direction && currentPosition + 1 > getChildCount() - 1)) {return;}if (direction) targetPosition = currentPosition + 1;else targetPosition = currentPosition - 1;currentProgress = positionOffset;//        Log.e(TAG, "direction:::" + direction + "     currentPosition:::" + currentPosition + "     targetPosition:::" + targetPosition);bezierCircular.setCurrentAndTarget(anchorList.get(currentPosition), anchorList.get(targetPosition));if (currentProgress == 0 && lastProgress > 0.9) {if (lastProgress > 0.9) {currentProgress = 1;}if (lastProgress < 0.1) {currentProgress = 0;}}bezierCircular.setProgress(direction ? currentProgress : currentProgress - 1);bezierPaint.setColor(circularColors.size() > 0 ? setCircularColor(direction ? currentProgress : 1 - currentProgress) : circularColor);invalidate();lastProgress = currentProgress;}

onDraw(Canvas canvas)

onDraw方法中代码就很少了

    @Overrideprotected void onDraw(Canvas canvas) {drawChildBg(canvas);bezierCircular.drawCircle(canvas, bezierPaint);drawClick(canvas);super.onDraw(canvas);}

附上子View背景绘制,及点击效果绘制代码

   //绘制子View的背景private void drawChildBg(Canvas canvas) {if (anchorList == null || anchorList.size() == 0) {Log.i(TAG, "锚点位置为空");return;}for (int i = 0; i < anchorList.size(); i++) {PointF pointF = anchorList.get(i);canvas.drawCircle(pointF.x, pointF.y, (childSideLength - 4) / 2, childBgPaint);}}//绘制点击效果private void drawClick(Canvas canvas) {PointF pointF = anchorList.get(targetPosition);canvas.drawCircle(pointF.x, pointF.y, clickRadius, clickPaint);}

效果

最终效果如下,可能与原概念图有些差距,但也算小有成就吧��

附上github地址:https://github.com/lichenming0516/BezierIndicator

小结

通过这两次自定义View的学习尝试,让自己对自定义View的绘制流程有了更深刻的了解,一些常见方法onMeasure()onLayout()onDraw()以及自定义属性的解析理解的更清晰一点。对于自定义View这座大山应该能算的上爬上半山腰了吧 ��

神奇的水滴效果导航栏-BezierIndicator相关推荐

  1. Flutter 凸起效果底部导航栏一

    大多app中都会带有底部导航栏,系统默认自带的导航条不够新颖.今天我们改造一下导航条,将中间的按钮起到凸起的效果,特别有的app,中间常用扫一扫功能. Flutter为我们提供了一个控件BottomN ...

  2. 底部导航栏使用BottomNavigationBar

    1.github地址 https://github.com/zhouxu88/BottomNavigationBar 2.基本使用 2,1添加依赖 implementation 'com.ashokv ...

  3. (翻译)导航栏按钮的5类常见设计错误

      用户依靠导航栏来查找所需的信息,如果导航栏按钮的标签和状态不明确,他们用着就很不方便.   以下列出设计人员设计导航栏按钮时的5类常见错误,如果你也存在这些问题,现在是时候让导航栏变得更明确了. ...

  4. iOS设置导航栏和状态栏

    文章目录 iOS 15 之后导航栏背景色的设置 1.状态栏设置 1.1.没有导航栏 1.2.有导航栏 2.导航栏背景和字体颜色 2.1.十六进制颜色转RGB 2.2.生成纯色图片 3.导航栏的另外一种 ...

  5. 用js实现导航栏shoufang效果_【读者投稿】用Github+docsify,我花了半天就搭好了个人博客...

    前言 "作为一个真正的码农,不能没有自己的个人博客",这是我说的.惭愧的是,入行两年多了都没搞起来,这让我一度怀疑自己是个假程序员.昨天终于克服了心里的"犹豫" ...

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

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

  7. php 实现tab切换_微信小程序实例:实现顶部tab切换以及滑动切换时导航栏会随着移动的效果(代码)...

    本篇文章给大家带来的内容是关于微信小程序实例:实现顶部tab切换以及滑动切换时导航栏会随着移动的效果(代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 实现的效果: js: Pa ...

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

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

  9. 网站导航栏如何设置更利于提升SEO优化效果?

    网站导航栏就像是用户浏览网站的"指南针",能够方便访客更直观的了解自己所在的位置,也能让访客更快速的找到想要了解的栏目.那么,导航栏该如何设置才能更利于SEO优化效果提升呢? 1. ...

最新文章

  1. Mail Archiving Expert电子邮件归档专家
  2. 重磅直播|大规模点云可视化技术
  3. 常见Android Native崩溃及错误原因
  4. 移动互联网教育领域或将出现新的风口?
  5. 编写代码,实现一个栈(Stack)的类。
  6. 从技术分工的角度来看996.ICU
  7. 碳达峰、碳中和带来的机遇和挑战研究报告
  8. 【ES6(2015)】解构赋值Desctructuring
  9. 消控中心人员配置_电气火灾监控系统在石药集团新药制剂配套特色原料药FDA生产中心项目...
  10. React中StrictMode严格模式
  11. 简书优秀IT专栏作者推荐
  12. php error file_get_contents()
  13. 网络安全法对计算机人员的影响,网络安全法的基本原则-网络安全论文-计算机论文.docx...
  14. 戒指戴在不同的手指上代表的意思
  15. java中特殊字符的输出方式_java 特殊符号输出绝对基础?
  16. 10天竟然只写了一行代码,谁的锅?
  17. 一些noip模拟题一句话题解
  18. 一副重现赤壁之战的神秘地图
  19. java实现斗地主发牌案例简单易懂
  20. Facebook路由事故未圆,何以元宇宙?

热门文章

  1. Android自定义控件系列二:自定义开关按钮
  2. 别了,指纹打卡!今后考勤可以刷脸了
  3. 索引+sql练习优化
  4. Vue项目二:设置标题搜索栏,以及图片的轮播。
  5. python-docx中文文档之文件类
  6. NtripShare EdgeEngine GNSS边缘解算盒子/模块/软件用户手册
  7. Google Dart新进展:Polymer代替Web UI
  8. Missing artifact com.microsoft.sqlserver:sqljdbc4:jar:4.0解决方案
  9. node+express+multer实现单个或多个图片文件,视频文件上传
  10. layui实现导出全部数据Excel