目录
自定义控件之QQ附近的人雷达扫描效果

前言

之前跟着康师傅学习 Android开发自定义控件系列课程时看到 笔记Android自定义控件的分类 里有这么一个效果,如下图


然后我通过搜索引擎搜到了这篇文章(Android超高仿QQ附近的人搜索展示),根据这篇文章我对源码进行了重构、优化,实现了 使用普通自定义 View 实现雷达扫描效果版本 和 使用自定义 SurfaceView 实现雷达扫描效果版本,具体实现过程就不讲了,同学们看代码和注释吧!


自定义 View 与 自定义 SurfaceView

那么,我们来看看 普通自定义 View 和 自定义 SurfaceView 之间的关系吧!

What is a SurfaceView ?

  • A view in your app's view hierarchy that has its own separate surface.
  • A view that directly accesses a lower-level drawing surface.
  • A view that is not part of the view hierarchy.
  • A view that can be drawn to from a separate thread.

什么是 SurfaceView ?

  • 应用程序的视图层次结构中的一个视图,它有自己独立的表面。
  • 直接访问低层绘图图面的视图。
  • 不属于视图层次结构的视图。
  • 可以从单独的线程绘制的视图。

What is the most distinguishing benefit of using a SurfaceView ?

  • A SurfaceView can make an app more responsive to user input.
  • You can move drawing operations away from the UI thread.
  • Your animations may run more smoothly.

使用 SurfaceView 最显著的好处是什么?

  • SurfaceView 可以让应用对用户输入的响应更灵敏。
  • 你可以把绘图操作从 UI 线程移开。
  • 你的动画可以运行得更流畅。

When should you consider using a SurfaceView ? Select up to three.

  • When your app does a lot of drawing, or does complex drawing.
  • When your app combines complex graphics with user interaction.
  • When your app uses a lot of images as backgrounds.
  • When your app stutters, and moving drawing off the UI thread could improve performance.

什么时候应该考虑使用 SurfaceView ?

  • 当你的应用需要大量绘图或复杂绘图时。
  • 当你的应用程序结合了复杂的图形和用户交互。
  • 当你的应用程序使用很多图片作为背景时。
  • 当你的应用程序出现问题时,移动 UI 线程可以提高性能。

以上摘抄自 谷歌官方文档 ,当然里面有一个小例子,同学们可以跟着官方的例子去实现自定义 SurfaceView 哈~


最显著的区别就是普通view和它的宿主窗口共享一个绘图表面(Surface),SurfaceView 虽然也在 View 的树形结构中,但是它有属于自己的绘图表面,Surface 内部持有一个 Canvas ,可以利用这个 Canvas 绘制。

摘抄自:SurfaceView和普通view的区别及简单使用


资源图片

universe_bg.jpg


circle_photo.png

使用普通自定义 View 实现雷达扫描效果版本

Kotlin
复制代码
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.View
import cn.cqautotest.imui.R
import kotlin.math.min

/**
 * 使用普通自定义 View 实现雷达扫描效果
 *
 * @property drawing Boolean
 * @property mPaintBackground Paint
 * @property mPaintLine Paint
 * @property mPaintCircle Paint
 * @property mPaintScan Paint
 * @property mDiameter Int
 * @property mRadius Int
 * @property mRect Rect
 * @property mMatrix Matrix
 * @property mCurrentScanRotateAngle Int
 * @property mBackgroundMipmap (android.graphics.Bitmap..android.graphics.Bitmap?)
 * @property mCenterBitmap Bitmap
 * @property mCircleProportions Array<Float>
 * @property mScanRotateSpeed Int
 * @property mCurrentScanCount Int
 * @property mMaxScanItemCount Int
 * @property mCurrentScanItem Int
 * @property mScan Boolean
 * @property mListener IScanListener
 */
class RadarScanView : View {

    // 是否绘制
    private var drawing: Boolean = false

    // 画背景需要用到的画笔
    private val mPaintBackground: Paint by lazy {
        Paint().apply {
            isAntiAlias = true
            style = Paint.Style.STROKE
        }
    }

    // 画圆线需要用到的画笔
    private val mPaintLine: Paint by lazy {
        Paint().apply {
            color = Color.parseColor("#3C8EAE")
            isAntiAlias = true
            strokeWidth = 1f
            style = Paint.Style.STROKE
        }
    }

    // 画圆需要用到的 paint画笔
    private val mPaintCircle: Paint by lazy {
        Paint().apply {
            color = Color.WHITE
            isAntiAlias = true
        }
    }

    // 画扫描需要用到的画笔
    private val mPaintScan: Paint by lazy {
        Paint().apply {
            style = Paint.Style.FILL_AND_STROKE
        }
    }

    // 圆的直径大小
    private var mDiameter: Int = 0

    // 圆的半径大小
    private var mRadius: Int = 0

    // 矩形
    private lateinit var mRect: Rect

    // 旋转需要使用到的矩阵
    private val mMatrix by lazy {
        Matrix()
    }

    // 当前旋转的角度
    private var mCurrentScanRotateAngle: Int = 0

    // 背景的图片资源
    private val mBackgroundMipmap = BitmapFactory.decodeResource(resources, R.drawable.universe_bg)

    // 中间的图片资源
    private var mCenterBitmap: Bitmap =
        BitmapFactory.decodeResource(resources, R.drawable.circle_photo)

    // 每层圆圈所占的比例
    private val mCircleProportions: Array<Float> =
        arrayOf(1 / 13f, 2 / 13f, 3 / 13f, 4 / 13f, 5 / 13f, 6 / 13f)

    // 扫描时的旋转速度
    private var mScanRotateSpeed = 5

    // 当前扫描的次数
    private var mCurrentScanCount: Int = 0

    // 最大扫描次数
    private var mMaxScanItemCount: Int = 1

    // 当前扫描显示的 Item
    private var mCurrentScanItem: Int = 0

    // 只有设置数据后才开始扫描
    private var mScan: Boolean = false

    // 扫描时的回调接口
    private lateinit var mListener: IScanListener

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    init {
        drawing = true
        // 保持屏幕常亮
        keepScreenOn = true
        // 设置在触摸模式下此视图是否可以接收焦点。将其设置为true也将确保该视图可聚焦。
        isFocusableInTouchMode = true
    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        drawing = true
        post(object : Runnable {
            override fun run() {
                if (drawing) {
                    calc()
                    invalidate()
                    // 可以通过改变 postDelayed() 方法的第二个参数 delayMillis 调节扫描的速度
                    postDelayed(this, 10)
                } else {
                    removeCallbacks(this)
                }
            }
        })
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        drawing = false
    }

    /**
     * 进行计算
     */
    private fun calc() {
        mCurrentScanRotateAngle = (mCurrentScanRotateAngle + mScanRotateSpeed) % 360
        mMatrix.postRotate(mScanRotateSpeed.toFloat(), mRadius.toFloat(), mRadius.toFloat())
        // 开始扫描显示标志为 true 且 只扫描一周
        if (mScan && mCurrentScanCount <= (360 / mScanRotateSpeed) && ::mListener.isInitialized) {
            if (mCurrentScanCount % mScanRotateSpeed == 0 && mCurrentScanItem < mMaxScanItemCount) {
                mListener.onScanning(mCurrentScanItem, mCurrentScanRotateAngle)
                mCurrentScanItem++
            }
            if (mCurrentScanItem == mMaxScanItemCount) {
                mListener.onScanSuccess()
            }
            mCurrentScanCount++
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        // 测量
        setMeasuredDimension(getMeasureSize(widthMeasureSpec), getMeasureSize(heightMeasureSpec))
    }

    /**
     * 获取尺寸
     *
     * @param measureSpec Int
     * @return Int
     */
    private fun getMeasureSize(measureSpec: Int): Int = run {
        val specMode = MeasureSpec.getMode(measureSpec)
        val specSize = MeasureSpec.getSize(measureSpec)
        if (specMode == MeasureSpec.EXACTLY) specSize else {
            // 指定测量大小
            if (specMode == MeasureSpec.AT_MOST) min(300, specSize) else 300
        }
    }

    /**
     * 当视图的大小发生变化时,这个方法会在布局过程中调用
     *
     * @param w Int
     * @param h Int
     * @param oldw Int
     * @param oldh Int
     */
    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mDiameter = min(w, h)
        mRadius = mDiameter / 2
        mRect = Rect(
            (mRadius - mDiameter * mCircleProportions[0]).toInt(),
            (mRadius - mDiameter * mCircleProportions[0]).toInt(),
            (mRadius + mDiameter * mCircleProportions[0]).toInt(),
            (mRadius + mDiameter * mCircleProportions[0]).toInt()
        )
        Log.d(TAG, "onSizeChanged: ====>w:$w mRadius:$mRadius mDiameter:$mDiameter")
    }

    /**
     * 在这个方法内进行重绘界面的操作
     *
     * @param canvas Canvas
     */
    override fun onDraw(canvas: Canvas) {
        // 绘制的层级由调用的先后顺序决定,先绘制的在下面,后绘制的在上面
        drawBackground(canvas)
        drawCircle(canvas)
        drawScan(canvas)
        drawCenterIcon(canvas)
    }

    /**
     * 绘制背景
     *
     * @param canvas Canvas
     */
    protected fun drawBackground(canvas: Canvas) {
        canvas.drawBitmap(mBackgroundMipmap, 0f, 0f, mPaintBackground)
    }

    /**
     * 绘制圆线圈
     *
     * @param canvas Canvas
     */
    private fun drawCircle(canvas: Canvas) {
        canvas.run {
            for (i in 1..5) {
                drawCircle(
                    mRadius.toFloat(),
                    mRadius.toFloat(),
                    mDiameter * mCircleProportions[i],
                    mPaintLine
                )
            }
        }
    }

    /**
     * 绘制扫描部分
     *
     * @param canvas Canvas
     */
    private fun drawScan(canvas: Canvas) {
        canvas.run {
            save()
            // 只赋值一次,避免在重绘时重复创建对象
            if (mPaintScan.shader == null) {
                mPaintScan.shader = SweepGradient(
                    mRadius.toFloat(), mRadius.toFloat(),
                    intArrayOf(Color.TRANSPARENT, Color.parseColor("#84B5CA")),
                    null
                )
            }
            concat(mMatrix)
            drawCircle(
                mRadius.toFloat(), mRadius.toFloat(), mDiameter * mCircleProportions[4],
                mPaintScan
            )
            restore()
        }
    }

    /**
     * 绘制中间的圆形图标
     *
     * @param canvas Canvas
     */
    private fun drawCenterIcon(canvas: Canvas) {
        canvas.drawBitmap(mCenterBitmap, null, mRect, mPaintCircle)
    }

    interface IScanListener {

        /**
         * 正在扫描(此时还没有扫描完毕)的监听
         *
         * @param position Int 扫描的位置
         * @param scanAngle Int 扫描的角度
         */
        fun onScanning(position: Int, scanAngle: Int)

        /**
         * 扫描成功时的回调
         */
        fun onScanSuccess()
    }

    companion object {
        private const val TAG = "RadarScanView"
    }
}

使用自定义 SurfaceView 实现雷达扫描效果版本

Kotlin
复制代码
import android.content.Context
import android.graphics.*
import android.os.Build
import android.util.AttributeSet
import android.view.SurfaceHolder
import android.view.SurfaceView
import androidx.annotation.RequiresApi
import cn.cqautotest.imui.R
import kotlin.math.min

/**
 * 使用自定义 SurfaceView 实现雷达扫描效果
 *
 * @property drawing Boolean
 * @property mSurfaceHolder SurfaceHolder
 * @property mPaintBackground Paint
 * @property mPaintLine Paint
 * @property mPaintCircle Paint
 * @property mPaintScan Paint
 * @property mWidth Int
 * @property mHeight Int
 * @property mMatrix Matrix
 * @property mCurrentScanRotateAngle Int
 * @property mBackgroundMipmap (android.graphics.Bitmap..android.graphics.Bitmap?)
 * @property mCenterBitmap Bitmap
 * @property mCircleProportions Array<Float>
 * @property mScanRotateSpeed Int
 * @property mCurrentScanCount Int
 * @property mMaxScanItemCount Int
 * @property mCurrentScanItem Int
 * @property mScan Boolean
 * @property mListener IScanListener
 */
class RadarScanSurfaceView : SurfaceView, SurfaceHolder.Callback, Runnable {

    // 是否绘制
    private var drawing: Boolean = false
    private val mSurfaceHolder: SurfaceHolder by lazy {
        holder
    }

    // 画背景需要用到的画笔
    private val mPaintBackground: Paint by lazy {
        Paint().apply {
            isAntiAlias = true
            style = Paint.Style.STROKE
        }
    }

    // 画圆线需要用到的画笔
    private val mPaintLine: Paint by lazy {
        Paint().apply {
            color = Color.parseColor("#3C8EAE")
            isAntiAlias = true
            strokeWidth = 1f
            style = Paint.Style.STROKE
        }
    }

    // 画圆需要用到的 paint画笔
    private val mPaintCircle: Paint by lazy {
        Paint().apply {
            color = Color.WHITE
            isAntiAlias = true
        }
    }

    // 画扫描需要用到的画笔
    private val mPaintScan: Paint by lazy {
        Paint().apply {
            style = Paint.Style.FILL_AND_STROKE
        }
    }

    // 圆的直径大小
    private var mDiameter: Int = 0

    // 圆的半径大小
    private var mRadius: Int = 0

    // 旋转需要使用到的矩阵
    private val mMatrix by lazy {
        Matrix()
    }

    // 当前旋转的角度
    private var mCurrentScanRotateAngle: Int = 0

    // 背景的图片资源
    private val mBackgroundMipmap = BitmapFactory.decodeResource(resources, R.drawable.universe_bg)

    // 中间的图片资源
    private var mCenterBitmap: Bitmap =
        BitmapFactory.decodeResource(resources, R.drawable.circle_photo)

    // 每层圆圈所占的比例
    private val mCircleProportions: Array<Float> =
        arrayOf(1 / 13f, 2 / 13f, 3 / 13f, 4 / 13f, 5 / 13f, 6 / 13f)

    // 扫描时的旋转速度
    private var mScanRotateSpeed = 5

    // 当前扫描的次数
    private var mCurrentScanCount: Int = 0

    // 最大扫描次数
    private var mMaxScanItemCount: Int = 1

    // 当前扫描显示的 Item
    private var mCurrentScanItem: Int = 0

    // 只有设置数据后才开始扫描
    private var mScan: Boolean = false

    // 扫描时的回调接口
    private lateinit var mListener: RadarScanSurfaceView.IScanListener

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    init {
        drawing = true
        // 保持屏幕常亮
        keepScreenOn = true
        // 设置在触摸模式下此视图是否可以接收焦点。将其设置为true也将确保该视图可聚焦。
        isFocusableInTouchMode = true
        //注册回调方法
        mSurfaceHolder.addCallback(this)
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        // 测量
        setMeasuredDimension(getMeasureSize(widthMeasureSpec), getMeasureSize(heightMeasureSpec))
    }

    /**
     * 获取尺寸
     *
     * @param measureSpec Int
     * @return Int
     */
    private fun getMeasureSize(measureSpec: Int): Int = run {
        val specMode = MeasureSpec.getMode(measureSpec)
        val specSize = MeasureSpec.getSize(measureSpec)
        if (specMode == MeasureSpec.EXACTLY) specSize else {
            if (specMode == MeasureSpec.AT_MOST) min(300, specSize) else 300
        }
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        mDiameter = min(w, h)
        mRadius = mDiameter / 2
    }

    /**
     * Surface 创建时触发
     *
     * @param holder SurfaceHolder
     */
    override fun surfaceCreated(holder: SurfaceHolder) {
        drawing = true
        // 创建子线程,在子线程中进行持续绘制
        Thread(this).start()
    }

    /**
     * Surface 大小或格式发生变化时触发,在 surfaceCreated() 方法调用后该函数至少会被调用一次
     *
     * @param holder SurfaceHolder
     * @param format Int
     * @param width Int
     * @param height Int
     */
    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {

    }

    /**
     * 销毁时触发,一般不可见时就会销毁
     *
     * @param holder SurfaceHolder
     */
    override fun surfaceDestroyed(holder: SurfaceHolder) {
        drawing = false
    }

    @RequiresApi(Build.VERSION_CODES.O)
    override fun run() {
        while (drawing) {
            mCurrentScanRotateAngle = (mCurrentScanRotateAngle + mScanRotateSpeed) % 360
            mMatrix.postRotate(mScanRotateSpeed.toFloat(), mRadius.toFloat(), mRadius.toFloat())
            // 捕获以下 lambda 内的所有异常
            runCatching {
                // 锁定并获取硬件画布
                val canvas = holder.lockHardwareCanvas()
                // 绘制的层级由调用的先后顺序决定,先绘制的在下面,后绘制的在上面
                drawBackground(canvas)
                drawCircle(canvas)
                drawScan(canvas)
                drawCenterIcon(canvas)
                // 可以通过阻塞子线程的时间来调节雷达扫描的速度
                Thread.sleep(20)
                // 解锁画布并发布
                holder.unlockCanvasAndPost(canvas)
            }
            // 开始扫描显示标志为 true 且 只扫描一周
            if (mScan && mCurrentScanCount <= (360 / mScanRotateSpeed) && ::mListener.isInitialized) {
                if (mCurrentScanCount % mScanRotateSpeed == 0 && mCurrentScanItem < mMaxScanItemCount) {
                    mListener.onScanning(mCurrentScanItem, mCurrentScanRotateAngle)
                    mCurrentScanItem++
                }
                if (mCurrentScanItem == mMaxScanItemCount) {
                    mListener.onScanSuccess()
                }
                mCurrentScanCount++
            }
        }
    }

    /**
     * 绘制背景
     *
     * @param canvas Canvas
     */
    protected fun drawBackground(canvas: Canvas) {
        canvas.drawBitmap(mBackgroundMipmap, 0f, 0f, mPaintBackground)
    }

    /**
     * 绘制圆线圈
     *
     * @param canvas Canvas
     */
    private fun drawCircle(canvas: Canvas) {
        canvas.run {
            for (i in 1..5) {
                drawCircle(
                    mRadius.toFloat(),
                    mRadius.toFloat(),
                    mDiameter * mCircleProportions[i],
                    mPaintLine
                )
            }
        }
    }

    /**
     * 绘制扫描部分
     *
     * @param canvas Canvas
     */
    private fun drawScan(canvas: Canvas) {
        canvas.run {
            save()
            // 只赋值一次
            if (mPaintScan.shader == null) {
                mPaintScan.shader = SweepGradient(
                    mRadius.toFloat(), mRadius.toFloat(),
                    intArrayOf(Color.TRANSPARENT, Color.parseColor("#84B5CA")),
                    null
                )
            }
            concat(mMatrix)
            drawCircle(
                mRadius.toFloat(),
                mRadius.toFloat(),
                mDiameter * mCircleProportions[5],
                mPaintScan
            )
            restore()
        }
    }

    /**
     * 绘制中间的圆形图标
     *
     * @param canvas Canvas
     */
    private fun drawCenterIcon(canvas: Canvas) {
        canvas.drawBitmap(
            mCenterBitmap, null,
            Rect(
                (mRadius - mDiameter * mCircleProportions[0]).toInt(),
                (mRadius - mDiameter * mCircleProportions[0]).toInt(),
                (mRadius + mDiameter * mCircleProportions[0]).toInt(),
                (mRadius + mDiameter * mCircleProportions[0]).toInt()
            ), mPaintCircle
        )
    }

    interface IScanListener {

        /**
         * 正在扫描(此时还没有扫描完毕)的监听
         *
         * @param position Int 扫描的位置
         * @param scanAngle Int 扫描的角度
         */
        fun onScanning(position: Int, scanAngle: Int)

        /**
         * 扫描成功时的回调
         */
        fun onScanSuccess()
    }

    companion object {
        private const val TAG = "RadarScanSurfaceView"
    }
}

实现效果图

GIF 图录制出来有点卡顿,所以就截图了,大家知道是在一直扫描的就好啦~



请同学们点赞、评论、打赏+关注啦~

本文由 A lonely cat 原创发布于 阳光沙滩 , 未经作者授权,禁止转载
评论
0 / 1024
断点
有丶东西
2021-02-10 09:43回复
回复@断点小时候看电视 雷达扫描可以扫出距离、位置还有方向 是不是很高大上 哈哈
2021-02-10 11:42回复
回复@你看的是,超人吧
2021-02-10 12:19回复
回复@断点不是 是那种现代战争片 记不得具体是哪部片子了
2021-02-10 12:46回复
拉大锯
这个不错,明天就过年了!祝你新年快乐!万事如意!
2021-02-10 08:24回复
回复@拉大锯也祝 康师傅 新的一年,开开心心、身体健康,Money多多 😏😏
2021-02-10 08:36回复
YanLQ
来了来了 虽迟但到
2021-02-10 06:37回复
回复@YanLQ能来就好 哈哈 离过年没几天了
2021-02-10 07:24回复
推荐文章
Linux 防火墙完全指南:从原理到多发行版实战
本文全面解析Linux防火墙的底层原理与主流工具配置,涵盖UFW、firewalld、iptables、nftables等方案,适合不同发行版用户。从基础概念到生产环境加固建议,助你掌握核心安全策略,确保服务器与网络的安全运行。
水上派对英语单词篇
探索FLOATOPIA水上派对的完整指南,涵盖签到流程、场地指引、安全须知、活动安排及夜场舞会等实用英语词汇与对话。无论是初学者还是老手,都能轻松掌握关键术语,享受完美水上体验。
麒麟操作系统下使用 virt-customize 重置虚拟机 root 密码实战
本文详细介绍了在麒麟操作系统环境下使用 virt-customize 工具重置 KVM 虚拟机 root 密码的完整流程,包含安装步骤、常见问题及解决方案,帮助运维人员高效处理密码遗忘或批量初始化场景,避免数据丢失。
浅析JS预编译-函数篇
本文详细讲解了JavaScript中函数的预编译过程,包括AO对象的创建、变量和函数声明的提升,以及执行阶段的变化。适合初学者了解JS执行机制,帮助深入理解代码运行逻辑。
访问Github的另一种姿势
本文介绍了如何通过Watt ToolKit等工具加速访问Github,适合对网络技术感兴趣的读者。内容涵盖工具介绍、下载和使用方法,以及一些实用技巧,值得一看。
使用Gulp压缩JS
本文详细介绍了如何使用Gulp压缩JavaScript文件,特别是支持ES6语法的处理方法。通过实际操作和效果对比,帮助开发者提升代码性能,适合对前端自动化工具感兴趣的读者。
使用Gulp压缩CSS
本文详细介绍了如何使用Gulp工具对CSS文件进行压缩,提升网页加载速度。适合需要优化静态资源的开发者,提供清晰的步骤和示例,帮助快速上手。
从0部署Nuxtjs项目
本文详细介绍了如何将Nuxt.js项目部署到公网,涵盖Node.js安装、环境配置、源码上传及使用PM2启动服务的全过程,适合开发者学习和实践。
Linux 命令行美化利器:tree 命令完全指南(安装 + 全部参数详解)
掌握 Linux 下的 `tree` 命令,快速直观地查看目录结构。本文详细讲解安装方法、参数含义及实战场景,适合开发者和系统管理员提升工作效率。
Linux常用命令(一)
本文详细介绍了Linux基础命令,包括文件操作、系统显示、网络状态、软件包管理等,适合初学者学习和查阅,是掌握Linux系统的实用指南。
大模型推理参数解码:温度、Top-p 与核采样如何塑造生成文本的灵魂
探索大语言模型的采样参数如何塑造生成内容,从温度到Top-p,从惩罚机制到熵控制,揭示背后的信息论原理与实践策略。了解如何通过参数调优,让AI既保持知识的严谨,又拥有创造力的自由。
记从0使用Claude code写代码
本文介绍了如何使用Claude Code搭配DeepSeek进行编程,详细讲解了安装Node.js、全局安装Claude、配置模型以及使用方法。对于开发者来说,这是一份实用的技术教程,尤其适合对AI编程工具感兴趣的读者。
Android-Studio-Gradle同步失败-Library为null的通用排查指南
遇到 Android Studio Gradle Sync 失败时,不要轻易删除全局缓存。本文详细解析了错误原因,并提供了一系列精准的排查步骤和解决方案,帮助开发者快速定位并解决问题,提升开发效率。
PHP实现密码加密
本文详细介绍了Bcrypt密码加密技术及其在PHP中的应用,帮助开发者提升用户密码的安全性。通过实际代码示例,展示了如何使用password_hash和password_verify函数进行密码加密与验证,是学习网络安全知识的实用指南。
WordCloud效果,滚动标签
本文详细介绍了如何在Vue项目中集成WordCloud组件,包括依赖安装、属性配置和使用方法。适合开发者学习如何实现数据可视化功能,提升项目交互体验。
从0使用WordPress搭建一个优美的网站
本文详细介绍了如何使用WordPress搭建一个美观的网站,从安装到主题配置和功能拓展,为读者提供了实用的操作指南。无论是初学者还是有一定经验的开发者,都能从中获得有价值的参考。
JS实现在网站底部添加运行时间
想知道如何在网站底部显示运行时间?本文详细讲解了通过JavaScript实现这一功能的方法,包括时间计算逻辑和代码实现。适合前端开发者学习参考,轻松为网站添加实用功能。
在Vercel上部署Hexo博客
本文详细介绍了如何在Vercel上快速部署Hexo博客,无需后端服务即可实现高效发布。相比传统方式,省去了手动生成和上传静态文件的步骤,更加便捷。适合想要搭建个人博客的开发者参考。
从0搭建一个Hexo博客
本文详细介绍了Hexo博客框架的使用方法,从安装到部署全流程讲解,适合想快速搭建个人博客的技术爱好者。内容清晰易懂,是入门Hexo的理想指南。
离线设备激活方案:古老的 Windows 光盘激活,离线算法授权等
本文深入解析了离线激活的核心原理与实现方式,从历史案例到现代技术,全面剖析了如何在无网络环境下确保软件授权的安全性。通过设备指纹、授权文件校验等手段,为开发者提供了可复用的架构设计思路,适用于工业、医疗等对网络依赖较低的场景。
Hexo实现生成站点地图
想为你的Hexo博客添加站点地图功能吗?本文详细介绍了如何通过安装插件和配置文件来实现,适合没有内置该功能的新主题或自定义主题的用户。简单步骤助你提升搜索引擎优化效果。
Hexo实现代码压缩
本文分享了如何通过Hexo插件优化博客性能,详细介绍了安装和配置过程,帮助提升网页加载速度。适合对网站优化感兴趣的开发者阅读。
记一次 GitHub 幽灵协作者大清洗:强制重写 Git 历史与穿透 CDN 缓存实践
本文详细讲解了如何解决GitHub上出现的‘幽灵协作者’问题,通过重写Git历史和穿透CDN缓存,彻底清理错误提交记录。适合开发者学习如何高效管理项目历史与优化仓库信息。
从一行 `native` 堆栈追到 InstallReferrer:一次主线程 ANR 的排查全过程
本文详细记录了一次主线程ANR的排查过程,从一行native堆栈追踪到InstallReferrer服务调用。通过分析堆栈、源码和系统调用,揭示了归因SDK在主线程同步调用Play商店服务导致的ANR问题,并提供了多维度的解决方案。对于Android开发者来说,是一篇深入浅出的技术实践指南。
Linux从 HelloWorld 到数据库服务注册
本文详细解析了Linux系统中服务注册的概念与实现,通过一个简单的Hello World示例,帮助读者理解如何将程序注册为systemd服务,并掌握相关操作命令。无论是数据库还是其他应用,了解服务注册机制都是系统管理的重要基础。
学习虚拟机的笔记
linux ps 命令详解,跟着敲一次就掌握了
深入了解 Linux 中最常用的进程查看命令 `ps`,掌握其各种用法和参数,适用于系统管理和故障排查。从基础到高级,全面解析 `ps` 的使用技巧,帮助您提升 Linux 运维技能。
ObjectMapper 入门:Java 对象与 JSON 之间的「翻译官」
了解ObjectMapper在Java中如何实现对象与JSON的转换,掌握其在Spring Boot项目中的应用及常见使用场景。本文详细解析了序列化/反序列化过程、API用法、与Spring MVC的关系以及与其他JSON库的对比,适合开发者快速上手和深入理解。
服务器一次中病毒的记录
本文详细描述了一次服务器异常流量的排查过程,发现大量外部IP与内部服务建立连接,疑似存在恶意程序。通过分析日志和图片,确认为恶意程序导致带宽占用过高,最终通过备份和删除操作解决问题。文章提供了技术排查思路和解决方案,对系统维护具有参考价值。
JavaWeb微服务脚手架搭建
本文介绍了构建微服务架构时常用的开发模板和核心组件,涵盖技术选型、依赖配置及版本差异分析。通过合理选择 Java 和 Spring Boot 版本,可以显著提升开发效率和系统性能,是开发者不可错过的实践指南。