Android 自定义控件基础

目录
一、前言
二、基础代码
(1)自定义View基础流程
(2)onDraw()方法
(3)onMeasure()方法
三、动画
(1)补间动画
(2)帧动画
(3)属性动画
四、SVG标签使用
五、练习Demo
六、Demo地址
七、内容推荐


一、前言

自定义控件一直是Android很重要的一部分,但是大部分时间我们都在处理业务逻辑而很少自己去写控件。因为现在开源的控件比较多,自定义写起来也比较麻烦。但是当我们需要的时候可能就忘了,所以菜鸟作者就买了本书打算系统的学习一遍。这一篇也算是《Android 自定义控件开发入门与实战》读后感。边学边工作也花了一个多月写过几篇笔记,不过后面的隐藏掉了。感觉有点啰嗦所以就凑成这一篇总结。

不管感兴趣与不感兴趣可以浏览一遍,积累一下基础。如果要深入学习这里也推荐两位大神博客

https://www.jianshu.com/p/146e5cec4863

**https://blog.csdn.net/harvic880925 **启舰《Android 自定义控件开发入门与实战》

二、基础代码

《 自定义控件开发入门与实战》这本书我这里主要分成两部分总结:1.自定义View 2.动画

这里主要根据练习时候写的Demo来进行基础的回顾。先从自定义View开始

(1)自定义View基础流程

  • 1.继承View,或者继承ViewGroup,以及View/ViewGroup的派生类
  • 2.测量 :onMeasure(),用来控制自定义View的宽高
  • 3.位置:onLayout(),在继承ViewGroup的时候需要重写该方法,用来控制子View摆放的位置。
  • 4.绘图:onDraw(),自定义View的主要方法,需要重载或重写该方法来绘制所需要的控件。

先从简单的开始讲起:

(2)onDraw()方法

为什么先说这方法呢 ? 因为前面两个方法不说。我们也可以通过这个方法来简单的实现自定义控件。

先上菜(代码),在介绍这道菜的好处

    fun initPaint(){
        //创建画笔
        paint = Paint()
        //设置画笔颜色
        paint?.setColor(Color.WHITE)
        //设置填充样式 1.Paint.Style.FILL仅填充内部  2.Paint.Style.FILL_AND_STROKE填充内部和描边 3.Paint.Style.STROKE仅描边
        paint?.setStyle(Paint.Style.STROKE)
        //设置画笔宽度 注:画笔样式为Paint.Style.FILL时不显示效果
        paint?.setStrokeWidth(5f)

        paint1 = Paint()
        //设置画笔颜色
        paint1?.setColor(Color.GREEN)
        //设置填充样式 1.Paint.Style.FILL仅填充内部  2.Paint.Style.FILL_AND_STROKE填充内部和描边 3.Paint.Style.STROKE仅描边
        paint1?.setStyle(Paint.Style.STROKE)
        //设置画笔宽度 注:画笔样式为Paint.Style.FILL时不显示效果
        paint1?.setStrokeWidth(5f)
    }
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        //平移画布 起始点起始点从(X:0,Y:0)变成(X:100,Y:100)
        canvas?.translate(0f, 100f)
        //裁剪画布 构造方法很多 都是以clip开头
        canvas?.clipRect(0, 0, 500, 500)
        //设置画布颜色
        canvas?.drawColor(Color.BLACK)
//        canvas.drawARGB(0,0,0,255);
//        canvas.drawRGB(0,0,255);

//        绘制直线 startX/startY:起始点X/Y坐标  stopX/stopY:终点X/Y坐标
        canvas?.drawLine(100f, 50f, 450f, 50f, paint)
        //绘制点
        canvas?.drawPoint(50f, 50f, paint)

        //绘制矩形====== >
        val rect = Rect(50, 100, 450, 450)//创建矩形工具类
        //绘制矩形方法
        canvas?.drawRect(rect, paint)//第一种构造方法
//        Rect/RectF用来保存int/float类型数值的矩形结构
//        RectF rectf = new RectF(200,200,400,400);
//        canvas.drawRect(rectf);第二种构造方法
//        canvas.drawRect(200,200,400,400,paint);第三种构造方法
        //   <====== 绘制矩形方法

//      起始点变成(X:50,Y:100)
        canvas?.translate(50f, 100f)
        //绘制路径 ====== >
        val path = Path()
        //设置起始点
        path.moveTo(100f, 50f)
        //第一条直线的终点也是第二条直线的起点
        path.lineTo(50f, 200f)
        path.lineTo(150f, 100f)
        path.lineTo(50f, 100f)
        path.lineTo(150f, 200f)
//        path.lineTo(100, 50);
        //闭环
        path.close()
        //绘制路径方法
        canvas?.drawPath(path, paint)
        // < ======绘制路径

        //绘制弧线  ===== >
        val path1 = Path()
        val rect1 = RectF(200f, 50f, 350f, 200f)
        //弧线主要方法 oval 生成椭圆的矩形,startAngle 弧开始角度, sweepAngle 持续角度
        path1.arcTo(rect1, 180f, 180f, false)
        canvas?.drawPath(path1, paint)
        // < ===== 绘制弧线

//      起始点变成(X:50,Y:100)
        canvas?.translate(50f, 250f)
        //绘制区域 ==== >
        val region = Region(0, 0, 200, 50)
        val region1 = Region(150, -50, 300, 50)
        drawRegion(canvas, region, paint)
        drawRegion(canvas, region1, paint1)
        //区域操作
        // (1)Op.DIFFERENCE:显示region与region1不同区域
        // (2)Op.INTERSECT:显示region与region1相交区域
        // (3)Op.UNION:显示region与region1组合在一起区域
        // (4)Op.XOR:显示region与region1相交之外区域
        // (5)Op.REVERSE_DIFFERENCE:显示region1与region不同区域
        // (6)Op.REPLACE:显示region1区域
        region.op(region1, Region.Op.INTERSECT)
        paint1?.setColor(Color.GRAY)
        drawRegion(canvas, region, paint1)
        // < ==== 绘制区域

        //保存当前画布状态
        canvas?.save()
        //恢复到上一层保存的状态
//        canvas.restore();
    }

    fun drawRegion(canvas : Canvas?,region : Region,paint: Paint?){
        var iter = RegionIterator(region)
        var r = Rect()
        while (iter.next(r)){
            canvas?.drawRect(r,paint)
        }
    }

看完代码我们知道,绘图的前提是先准备好一只画笔(paint),要准备一只怎样的画笔呢。这时候我们可以根据Paint 类提供的API来定义画笔样式。

画笔准备好之后,我们就要考虑绘制什么样的图形呢,要绘制在什么地方。这时候我们需要一张画布,用来显示绘制的图形。

而onDraw(canvas: Canvas?)方法刚好提供了一个画布,并且通过Canvas类提供的API,我们可以绘制各式各样的图形。当然常用的绘图方法就看上面的代码。都用详细的注释。

总结:1.准备画笔 2.利用画布绘图

注意:通常要在自定义View的构造函数中初始化好画笔,不要在onDraw()方法中去初始画笔。因为每次刷新画布的时候都会不断调用ondraw()创建新的画笔,消耗内存....

(3)onMeasure()方法

如果要通过下面属性来控制自定义View的大小时,我们需要重载onMeasure()方法来控制画布(自定义View)的大小,

android:layout_width="200dp"
android:layout_height="250dp"

怎么控制呢。

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        //widthMeasureSpec/heightMeasureSpec 由mode+size两部分组成
        //获取模式
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        //获取数值
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)

        when(widthMode){
            //父元素决定子元素的确切大小,子元素将被限定在给定的边界里而忽略它本身的大小 :match_parent 或者具体指 200dp
            MeasureSpec.EXACTLY ->{
                //根据需求计算宽度大小
                 testWidth = widthSize - 100
            }
            //子元素至多达到指定大小的值 : wrap_content
            MeasureSpec.AT_MOST ->{}
            //父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小
            MeasureSpec.UNSPECIFIED ->{}
        }

        when(heightMode){
            MeasureSpec.EXACTLY ->{
                //根据需求计算高度大小
                testHeight = heightSize - 100
            }
            MeasureSpec.AT_MOST ->{}
            MeasureSpec.UNSPECIFIED ->{}
        }
        //计算出的宽度和高度 可以通过setMeasuredDimension方法进行设置
        setMeasuredDimension(testWidth!!, testHeight!!)
    }

通过上面的代码我们可以简单的计算出所需要的宽高。

只通过上面简单的代码很难明白测量的意义,所以这里提供一个博客供大家参考,篇幅太长就不在这里多写 毕竟是总结..

https://www.cnblogs.com/yishujun/p/5560838.html

onLayout()方法在这里也不介绍,因为只用在自定义ViewGroup的时候才会使用。逻辑也会比较复杂。

三、动画

这本书好几张都讲了动画,不过这里不打算详细介绍。自定义View才是重点。所以这边只贴一部分常用代码。供回忆用。

(1)补间动画

(代码使用这里就不贴了)

<!--透明度-->
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="3000"
    android:fromAlpha="0"
    android:toAlpha="1"
    android:fillAfter = "true"
    android:repeatCount = "infinite"
    android:repeatMode = "reverse"
    android:interpolator = "@android:anim/accelerate_interpolator"
    />
<!--旋转-->
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="3000"
    android:fromDegrees="0"
    android:toDegrees="360"
    android:pivotX="50%"
    android:pivotY="50%"
    android:fillAfter = "true"
    android:repeatCount = "infinite"
    android:repeatMode = "reverse"
    android:interpolator = "@android:anim/linear_interpolator"
/>
<!--缩放-->
<scale xmlns:android="http://schemas.android.com/apk/res/android"
        android:duration="3000"
        android:fromXScale="0"
        android:toXScale="2"
        android:fromYScale="0"
        android:toYScale="2"
        android:pivotX="50%"
        android:pivotY="50%"
        android:fillAfter = "true"
        android:repeatCount = "infinite"
        android:repeatMode = "reverse"
        android:interpolator = "@android:anim/linear_interpolator"
    />
<!--移动-->
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXDelta="0"
    android:toXDelta="1000"
    android:duration="3000"
    android:fillAfter = "true"
    android:repeatCount = "infinite"
    android:repeatMode = "reverse"
    android:interpolator = "@android:anim/linear_interpolator"
/>
<!--组合动画-->
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:duration="3000"
     android:fillAfter="true"
     android:interpolator="@android:anim/linear_interpolator"
     android:repeatMode="reverse"
>
    <alpha
            android:fromAlpha="0"
            android:toAlpha="1"
            android:repeatCount="infinite"
    />
    <rotate
            android:pivotX="50%"
            android:pivotY="50%"
            android:fromDegrees="0"
            android:toDegrees="360"
            android:repeatCount="infinite"
    />
    <scale
            android:fromXScale="0"
            android:fromYScale="0"
            android:pivotY="50%"
            android:toXScale="1"
            android:toYScale="1"
            android:repeatCount="infinite"
    />
    <translate
            android:fromXDelta="0"
            android:toXDelta="1000"
            android:repeatCount="infinite"
    />
</set>

使用方式:

val loadAnimation = AnimationUtils.loadAnimation(this, R.anim.alpha)
view.startAnimation(TweenAnimation.getAlpha())

(2)帧动画

object FrameAnimation {
    /**
     * AnimationDrawable常用属性
     * start():开始播放逐帧动画。
     * stop():停止播放逐帧动画。
     * getDuration(int index):得到指定index的帧的持续时间
     * getFrame(int index):得到指定index的帧所对应的Drawable对象
     * getNumberOfFrames():得到当前AnimationDrawable的所有帧数量
     * isRunning():判断当前AnimationDrawable是否正在播放
     * setOneShot(boolean oneShot):设置AnimationDrawable是否执行一次
     * isOneShot():判断当前AnimationDrawable是否执行一次
     * addFrame(Drawable frame,int duration):为AnimationDrawable添加1帧,并设置持续时间
     */
    fun getAnimation(context: Context):AnimationDrawable{
        val animationDrawable = AnimationDrawable()
        for (x in 1..3){
            val resources = context.resources.getIdentifier("frame"+x,"mipmap",context.packageName)
            val drawable = context.resources.getDrawable(resources)
            animationDrawable.addFrame(drawable,500)
        }
        animationDrawable.isOneShot = false
        return animationDrawable
    }
}
image.gif
使用方式:val animation = FrameAnimation.getAnimation(this)iv_main.setBackgroundDrawable(animation)animation.start()

(3)属性动画

object ValueAnimation {
    /**
     * ValueAnimator常用函数
     * ofInt(int... values)、ofFloat(float... values) 、ofObject:设置动画变化过程中的值
     * setDuration(long duration):设置动画时长,单位是毫秒
     * getAnimatedValue():获取ValueAnimator在运动时当前运动点的值
     * start():开始动画
     * setRepeatCount(int value):设置循环次数,设置为INFINITE表示无限循环
     * setRepeatMode(int value):设置循环模式 ValueAnimation.REVERSE倒序重新开始 ValueAnimation.RESTART正序重新开始
     * addUpdateListener(AnimatorUpdateListener listener) : 监听动画过程中值的实时变化
     * addListener(AnimatorListener listener):监听动画变化时的4个状态
     * removeUpdateListener(AnimatorUpdateListener listener):移除指定监听
     * removeAllUpdateListeners():移除所有监听
     * removeListener(AnimatorListener listener):移除AnimatorUpdateListener
     * removeAllListeners():移除AnimatorListener
     * setInterpolator():设置插值器
     * cancel():取消动画
     * setStartDelay(long startDelay):延时多久开始,单位是毫秒
     * clone():克隆一个ValueAnimator实例
     */
    fun init(view : View):ValueAnimator{
        val animator = ValueAnimator.ofInt(0, 500)

        animator.duration = 3000
        animator.repeatCount = ValueAnimator.INFINITE
        animator.repeatMode = ValueAnimator.REVERSE
        animator.setEvaluator(MyEvaluator)
        animator.addUpdateListener({animation ->
            val value = animation.animatedValue as Int
            view.layout(view.left,view.top,value+view.left,view.bottom)
        })
        return animator
    }

    /**
     * 开始动画
     */
    fun startAnimator(view : View){
        val init = init(view)
        init.start()
    }

    /**
     * 停止动画
     */
    fun  stopAnimator(view : View){
        val init = init(view)
        init.removeAllUpdateListeners()
        init.cancel()
    }
}
使用方式:ValueAnimation.startAnimator(iv_main)

ObjectAnimation代码使用:

object ObjectAnimation {
    /**
     * 组合动画
     */
    fun start(tv1 : View){
        var background = ObjectAnimator.ofInt(tv1,"BackgroundColor", Color.BLACK,Color.BLUE,Color.RED)
        background.setRepeatCount(ValueAnimator.INFINITE)
        var alpha = ObjectAnimator.ofFloat(tv1,"alpha",0f,1f,0f,0.5f)
        alpha.setRepeatCount(ValueAnimator.INFINITE)
        var animatorSet = AnimatorSet()
        animatorSet.playTogether(background,alpha)
        animatorSet.setDuration(3000)
        animatorSet.start()
    }

    /**
     * PropertyValuesHolder 保存了动画过程中所需要操作的属性和对应的值。
     * //设置动画的Evaluator
     * public void setEvaluator(TypeEvaluator evaluator)
     * //用于设置ofFloat()所对应的动画值列表
     * public void setFloatValues(float... values)
     * //用于设置ofInt()所对应的动画值列表
     * public void setIntValues(int... values)
     * //用于设置ofKeyframes()所对应的动画值列表
     * public void setKeyframes(Keyframe... values)
     * //用于设置ofObject()所对应的动画值列表
     * public void setObjectValues(Object... values)
     * //设置动画属性名
     * public void setPropertyName(String propertyName)
     */
    fun property(view : View){
        val Background = PropertyValuesHolder.ofInt("BackgroundColor", Color.BLACK,Color.BLUE,Color.RED)
        val alpha = PropertyValuesHolder.ofFloat("alpha",0f,1f,0f,0.5f)
        val holder = ObjectAnimator.ofPropertyValuesHolder(view,Background, alpha)
        holder.duration = 3000
        holder.interpolator = AccelerateInterpolator()
        holder.repeatCount = Animation.INFINITE
        holder.repeatMode = REVERSE
        holder.start()
    }
    /**
     * KeyFrame 提供方便地控制动画速率问题。
     */
    fun keyFrame(view : View){
        val holder = PropertyValuesHolder.ofFloat("alpha", 0f,1f)
        //fraction表示当前的显示进度  value:表示动画当前所在的数值位置。
        val keyframe = Keyframe.ofFloat(0.1f, 0.1f)
        PropertyValuesHolder.ofKeyframe("alpha",keyframe)
        val valuesHolder = ObjectAnimator.ofPropertyValuesHolder(view, holder)
        valuesHolder.duration = 3000
        valuesHolder.repeatCount = Animation.INFINITE
        valuesHolder.repeatMode = REVERSE
        valuesHolder.start()
    }

    /**
     * ViewPropertyAnimator Android3.1中新增ViewPropertyAnimator机制,给默认属性提供了一种更加便捷的用法。
     */
    fun viewProperty(view : View){
        view.animate().alpha(0.5f).translationX(500f).rotation(180f).setDuration(5000).scaleX(2f)
    }
}

使用方式:

 R.id.btn_propertyValuesHolder ->{
                ObjectAnimation.property(iv_main)
            }
            R.id.btn_keyFrame ->{
                ObjectAnimation.keyFrame(iv_main)
            }
            R.id.btn_animate ->{
                ObjectAnimation.viewProperty(iv_main)
            }
objectAnimator文件使用:
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:valueType="floatType"
                android:valueFrom="1"
                android:valueTo="3"
                android:startOffset="0"
                android:duration="2000"
                android:interpolator="@android:anim/linear_interpolator"
                android:propertyName="scaleY"
                android:repeatCount="infinite"
                android:repeatMode="reverse"/>

使用方式

//xml动画
val loadAnimator = AnimatorInflater.loadAnimator(this, R.animator.object_animation)
loadAnimator.setTarget(iv_main)
loadAnimator.start()

不解释光贴代码可能看起来很乱,但动画确实不是这篇的重点,只是书中有提到。所以把练习时写的代码给贴出来好复习。如果看起来很累大家可以跳过这一部分。

四、SVG标签使用

Google在Android 5.0中增加了对SVG图形的支持。因为SVG的占用空间比Bitmap小。所以这里也稍微提一下使用方式。

首先定义SVG标签:

svg_vector.xml

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="100dp"
    android:height="50dp"
    android:viewportWidth="100"
    android:viewportHeight="100"
>
    <path
            android:name="bar"
            android:pathData="M0,23 L60,23 L30,90 Z"
            android:strokeWidth="2"
            android:strokeColor="@android:color/darker_gray"/>
</vector>
        <!--width与height属性:表示该SVG图形的具体大小。-->
        <!--viewportWidth与viewportHeight属性:表示SVG图形划分的比例。-->
        <!--path中字母M表示moveTo,字母L表示lineTo。-->
        <!--vector标签指定的是画布带下,而path标签则指定的是路径内容-->
        <!--(2)path标签-->
        <!--android:name:声明一个标记,类似于ID,便于对其做动画的时候顺利地找到该节点。-->
        <!--android:pathData:对SVG矢量图的描述。-->
        <!--android:strokeWidth:画笔的宽度。-->
        <!--android:fillColor:填充颜色。-->
        <!--android:fillAlpha:填充颜色的透明度。-->
        <!--android:strokeColor:描边颜色。-->
        <!--android:strokeWidth:描边宽度。-->
        <!--android:strokeAlpha:描边透明度。-->
        <!--android:strokeLineJoin:用于指定折线拐角形状,取值有miter、round、bevel。-->
        <!--android:strokeLineCap:画出线条的终点形状,取值有butt/round/square-->
        <!--android:strokeMiterLimit:设置斜角的上限。-->
        <!--android:trimPathStart:用于指定路径从哪开始,取值为0~1,表示路径开始位置的百分比。-->
        <!--android:trimPathEnd:用于指定路径的结束位置,取值为0~1,表示路径结束位置的百分比。-->
        <!--android:trimPathOffset:用于指定结果路径的位移距离,取值为0~1,当取值为0时,不进行位移;当取值为1时,位移整条路径的长度。-->
        <!--android:pathData:在path标签中,主要通过pathData属性来指定SVG图像的显示内容。-->

        <!--pathData属性除M和L指令以外,还有更多的指令:-->
        <!--M = moveto(M X,Y):将画笔移动到指定的坐标位置。-->
        <!--L = lineto(L X,Y):画直线到指定的坐标位置。-->
        <!--H = horizontal lineto(H X):画水平线到指定的X坐标位置。-->
        <!--V = vertical lineto(V Y):画垂直线到指定的Y坐标位置。-->
        <!--C = curveto(C X1,Y1,X2,Y2,ENDX,ENDY):三阶贝济埃曲线。-->
        <!--S = smooth curveto(S X2,Y2,ENDX,ENDY):三阶贝济埃曲线。-->
        <!--Q = quadratic Belzier curve(Q X,Y,ENDX,ENDY):二阶贝济埃曲线。-->
        <!--T = smooth quadratic Belzier curveto(T ENDX,ENDY):映射前面路径后的终点。-->
        <!--A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧线。-->
        <!--Z = closepath():关闭路径。-->

        <!--group标签常用属性:-->
        <!--android:name:组的名字,用于与动画相关联-->
        <!--android:rotation:指定该组图像的旋转度数-->
        <!--android:pivotX:定义缩放和旋转改组时的X参考点-->
        <!--android:pivotY:定义缩放和旋转该组时的Y参考点-->
        <!--android:scaleX:指定该组X轴缩放大小-->
        <!--android:scaleY:指定该组Y轴缩放大小-->
        <!--android:translateX:指定该组沿X轴平移的距离-->
        <!--android:translateY:指定该组沿Y轴平移的距离-->

标签与动画绑定在一起

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
                 android:drawable="@drawable/svg_vector">
    <target
            android:name="bar"
            android:animation="@animator/svg_animation"/>

</animated-vector>

svg_animation.xml

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:propertyName="trimPathStart"
                android:valueFrom="0"
                android:valueTo="1"
                android:duration="2000"/>

使用方式:

val create = AnimatedVectorDrawableCompat.create(this, R.drawable.svg_animation)iv_main.setImageDrawable(create)

简单略过...,一切解释尽在代码中

五、练习Demo

看完这本书后发现了几个有趣的控件。例:放大镜 ,刮刮卡

这里就不展示代码了,都写在练习的项目当中,若有兴趣可以下载下来看看。

六、Demo地址

练习Demo地址Github:https://github.com/DayorNight/CustomView

另外介绍个人开发的一个Android工具类

Github:https://github.com/DayorNight/BLCS

apk下载体验地址:https://www.pgyer.com/BLCS

也可以扫码下载

七、内容推荐

CSDN:《Android 自定义控件基础》
《Android 数据库知识回顾》​​​​​​​
《Android 《Android移动性能实战》学习笔记》
《Android 下载安装应用APK封装(适配8.0)》
《Android Notification通知简单封装(适配8.0)​​​​​​​》​​​​​​​
《Android 仿RxDialog自定义DialogFragment》

如果你觉得写的不错或者对您有所帮助的话

不妨顶一个【微笑】,别忘了点赞、收藏、加关注哈

看在花了这么多时间整理写成文章分享给大家的份上,记得手下留情哈

您的每个举动都是对我莫大的支持

image
image.gif

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 最近大家都在@微信君,请它老人家送顶圣诞帽!无意间发现有些APP可以自行添加各类圣诞帽,红绿自选… 制作步骤如下:...
    小鹿美洋洋阅读 386评论 0 0
  • 四种力量拔除自大的坏种子 事件 与员工沟通时,会有态度不好不耐烦表现 思维空性 我对他人的态度来自于我自大的坏种子...
    里喻棋阅读 143评论 0 0
  • 呆迷,今天我们再开圣诞晚会,不知道那来的那么多情绪,突然感觉做人好难,难到忘了自己的梦想,止于此步,但是尽管如此,...
    沫小凯阅读 249评论 0 0
  • 晚上的时候我常常坐在窗台边,看着一轮明月从树叶底下慢慢升起来。千百年来,月亮就这样静静的升上来,没有悲,没有喜,悄...
    播音1801B伍洁阅读 137评论 0 0