Android滑动列表索引波浪侧边栏与电话拨打功能

news2024/11/21 1:41:08

✍️作者简介:大鹏编码(专注于HarmonyOS、Java、Android、Web、TCP/IP等技术方向)
🐳博客主页: 开源中国、稀土掘金、51cto博客、知乎、简书、CSDN
🔔如果文章对您有一定的帮助请👉关注✨、点赞👍、收藏📂、评论💬。
🔥如需转载请参考【转载须知】

文章目录

      • 引言
      • 波浪侧边栏
        • 1. 什么是波浪侧边栏?
        • 2. 如何实现?
        • 3. 优势和冲击力
      • 代码实现
        • 波浪侧边栏代码
        • 适配器代码
        • 实例
      • 总结

老样子先看实例:
在这里插入图片描述

引言

Android应用中实现滑动列表索引波浪侧边栏,同时支持根据拼音分类的快速查找,并且具备拨打电话的功能。


波浪侧边栏

1. 什么是波浪侧边栏?

滑动列表索引波浪侧边栏是一种通过滑动屏幕侧边实现快速导航的交互设计。这种设计通常以字母或拼音为基础,让用户可以快速浏览和查找列表中的内容。

2. 如何实现?

在Android应用中,可以使用RecyclerView和定制的侧边栏组件来实现滑动列表索引波浪侧边栏。通过监听滑动事件和触摸事件,实时更新列表的位置,从而实现侧边栏与列表的同步操作。

3. 优势和冲击力
  • 提升用户导航效率:用户无需逐一滚动列表,通过直接点击侧边栏上的字母或拼音,即可快速跳转到相应位置,提升了用户查找内容的效率。

  • 引人注目的波浪动画:为了增强用户体验,可以添加波浪状的动画效果,使侧边栏更加生动有趣,吸引用户的注意力。

代码实现

需要的引用包build.gradle.kts

    implementation("androidx.recyclerview:recyclerview:1.2.1")
    implementation("com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.47")
    implementation("com.google.code.gson:gson:2.10.1")
    implementation("com.orhanobut:logger:2.2.0")
    implementation("com.belerweb:pinyin4j:2.5.1")

注意:这里BaseRecyclerViewAdapterHelper要在settings.gradle.kts
引用maven { url = uri("https://jitpack.io") }

波浪侧边栏代码
public class WaveSideBar extends View {

    private static final String TAG = "WaveSideBar";

    // 计算波浪贝塞尔曲线的角弧长值
    private static final double ANGLE = Math.PI * 45 / 180;
    private static final double ANGLE_R = Math.PI * 90 / 180;
    private OnTouchLetterChangeListener mListener;

    // 渲染字母表
    private List<String> mLetters;

    // 当前选中的位置
    private int mChoosePosition = -1;

    private int mOldPosition;

    private int mNewPosition;

    // 字母列表画笔
    private Paint mLettersPaint = new Paint();

    // 提示字母画笔
    private Paint mTextPaint = new Paint();
    // 波浪画笔
    private Paint mWavePaint = new Paint();

    private int mTextSize;
    private int mHintTextSize;
    private int mTextColor;
    private int mWaveColor;
    private int mTextColorChoose;
    private int mWidth;
    private int mHeight;
    private int mItemHeight;
    private int mPadding;

    // 波浪路径
    private Path mWavePath = new Path();

    // 圆形路径
    private Path mCirclePath = new Path();

    // 手指滑动的Y点作为中心点
    private int mCenterY; //中心点Y

    // 贝塞尔曲线的分布半径
    private int mRadius;

    // 圆形半径
    private int mCircleRadius;
    // 用于过渡效果计算
    private ValueAnimator mRatioAnimator;

    // 用于绘制贝塞尔曲线的比率
    private float mRatio;

    // 选中字体的坐标
    private float mPointX, mPointY;

    // 圆形中心点X
    private float mCircleCenterX;

    public WaveSideBar(Context context) {
        this(context, null);
    }

    public WaveSideBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WaveSideBar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        mLetters = Arrays.asList(context.getResources().getStringArray(R.array.waveSideBarLetters));

        mTextColor = Color.parseColor("#969696");
        mWaveColor = Color.parseColor("#bef9b81b");
        mTextColorChoose = context.getResources().getColor(android.R.color.white);
        mTextSize = context.getResources().getDimensionPixelSize(R.dimen.textSize);
        mHintTextSize = context.getResources().getDimensionPixelSize(R.dimen.hintTextSize);
        mPadding = context.getResources().getDimensionPixelSize(R.dimen.padding);
        if (attrs != null) {
            TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.waveSideBar);
            mTextColor = a.getColor(R.styleable.waveSideBar_textColor, mTextColor);
            mTextColorChoose = a.getColor(R.styleable.waveSideBar_chooseTextColor, mTextColorChoose);
            mTextSize = a.getDimensionPixelSize(R.styleable.waveSideBar_textSize, mTextSize);
            mHintTextSize = a.getDimensionPixelSize(R.styleable.waveSideBar_hintTextSize, mHintTextSize);
            mWaveColor = a.getColor(R.styleable.waveSideBar_backgroundColor, mWaveColor);
            mRadius = a.getDimensionPixelSize(R.styleable.waveSideBar_radius, context.getResources().getDimensionPixelSize(R.dimen.radius));
            mCircleRadius = a.getDimensionPixelSize(R.styleable.waveSideBar_circledRadius, context.getResources().getDimensionPixelSize(R.dimen.circleRadius));
            a.recycle();
        }

        mWavePaint = new Paint();
        mWavePaint.setAntiAlias(true);
        mWavePaint.setStyle(Paint.Style.FILL);
        mWavePaint.setColor(mWaveColor);

        mTextPaint.setAntiAlias(true);
        mTextPaint.setColor(mTextColorChoose);
        mTextPaint.setStyle(Paint.Style.FILL);
        mTextPaint.setTextSize(mHintTextSize);
        mTextPaint.setTextAlign(Paint.Align.CENTER);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        final float y = event.getY();
        final float x = event.getX();
        mOldPosition = mChoosePosition;
        mNewPosition = (int) (y / mHeight * mLetters.size());
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //限定触摸范围
                if (x < mWidth - 1.5 * mRadius) {
                    return false;
                }
                mCenterY = (int) y;
                startAnimator(1.0f);

                break;
            case MotionEvent.ACTION_MOVE:

                mCenterY = (int) y;
                if (mOldPosition != mNewPosition) {
                    if (mNewPosition >= 0 && mNewPosition < mLetters.size()) {
                        mChoosePosition = mNewPosition;
                        if (mListener != null) {
                            mListener.onLetterChange(mLetters.get(mNewPosition));
                        }
                    }
                }
                invalidate();
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:

                startAnimator(0f);
                mChoosePosition = -1;
                break;
            default:
                break;
        }
        return true;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mHeight = MeasureSpec.getSize(heightMeasureSpec);
        mWidth = getMeasuredWidth();
        mItemHeight = (mHeight - mPadding) / mLetters.size();
        mPointX = mWidth - 1.6f * mTextSize;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制字母列表
        drawLetters(canvas);

        //绘制波浪
        drawWavePath(canvas);

        //绘制圆
        drawCirclePath(canvas);

        //绘制选中的字体
        drawChooseText(canvas);

    }

    /**
     * 绘制字母列表
     *
     * @param canvas
     */
    private void drawLetters(Canvas canvas) {

        RectF rectF = new RectF();
        rectF.left = mPointX - mTextSize;
        rectF.right = mPointX + mTextSize;
        rectF.top = mTextSize / 2;
        rectF.bottom = mHeight - mTextSize / 2;

        mLettersPaint.reset();
        mLettersPaint.setStyle(Paint.Style.FILL);
        mLettersPaint.setColor(Color.parseColor("#F9F9F9"));
        mLettersPaint.setAntiAlias(true);
        canvas.drawRoundRect(rectF, mTextSize, mTextSize, mLettersPaint);

        mLettersPaint.reset();
        mLettersPaint.setStyle(Paint.Style.STROKE);
        mLettersPaint.setColor(mTextColor);
        mLettersPaint.setAntiAlias(true);
        canvas.drawRoundRect(rectF, mTextSize, mTextSize, mLettersPaint);

        for (int i = 0; i < mLetters.size(); i++) {
            mLettersPaint.reset();
            mLettersPaint.setColor(mTextColor);
            mLettersPaint.setAntiAlias(true);
            mLettersPaint.setTextSize(mTextSize);
            mLettersPaint.setTextAlign(Paint.Align.CENTER);

            Paint.FontMetrics fontMetrics = mLettersPaint.getFontMetrics();
            float baseline = Math.abs(-fontMetrics.bottom - fontMetrics.top);

            float pointY = mItemHeight * i + baseline / 2 + mPadding;

            if (i == mChoosePosition) {
                mPointY = pointY;
            } else {
                canvas.drawText(mLetters.get(i), mPointX, pointY, mLettersPaint);
            }
        }

    }

    /**
     * 绘制选中的字母
     *
     * @param canvas
     */
    private void drawChooseText(Canvas canvas) {
        if (mChoosePosition != -1) {
            // 绘制右侧选中字符
            mLettersPaint.reset();
            mLettersPaint.setColor(mTextColorChoose);
            mLettersPaint.setTextSize(mTextSize);
            mLettersPaint.setTextAlign(Paint.Align.CENTER);
            canvas.drawText(mLetters.get(mChoosePosition), mPointX, mPointY, mLettersPaint);

            // 绘制提示字符
            if (mRatio >= 0.9f) {
                String target = mLetters.get(mChoosePosition);
                Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
                float baseline = Math.abs(-fontMetrics.bottom - fontMetrics.top);
                float x = mCircleCenterX;
                float y = mCenterY + baseline / 2;
                canvas.drawText(target, x, y, mTextPaint);
            }
        }
    }

    /**
     * 绘制波浪
     *
     * @param canvas
     */
    private void drawWavePath(Canvas canvas) {
        mWavePath.reset();
        // 移动到起始点
        mWavePath.moveTo(mWidth, mCenterY - 3 * mRadius);
        //计算上部控制点的Y轴位置
        int controlTopY = mCenterY - 2 * mRadius;

        //计算上部结束点的坐标
        int endTopX = (int) (mWidth - mRadius * Math.cos(ANGLE) * mRatio);
        int endTopY = (int) (controlTopY + mRadius * Math.sin(ANGLE));
        mWavePath.quadTo(mWidth, controlTopY, endTopX, endTopY);

        //计算中心控制点的坐标
        int controlCenterX = (int) (mWidth - 1.8f * mRadius * Math.sin(ANGLE_R) * mRatio);
        int controlCenterY = mCenterY;
        //计算下部结束点的坐标
        int controlBottomY = mCenterY + 2 * mRadius;
        int endBottomX = endTopX;
        int endBottomY = (int) (controlBottomY - mRadius * Math.cos(ANGLE));
        mWavePath.quadTo(controlCenterX, controlCenterY, endBottomX, endBottomY);

        mWavePath.quadTo(mWidth, controlBottomY, mWidth, controlBottomY + mRadius);

        mWavePath.close();
        canvas.drawPath(mWavePath, mWavePaint);
    }

    /**
     * 绘制左边提示的圆
     *
     * @param canvas
     */
    private void drawCirclePath(Canvas canvas) {
        //x轴的移动路径
        mCircleCenterX = (mWidth + mCircleRadius) - (2.0f * mRadius + 2.0f * mCircleRadius) * mRatio;

        mCirclePath.reset();
        mCirclePath.addCircle(mCircleCenterX, mCenterY, mCircleRadius, Path.Direction.CW);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            mCirclePath.op(mWavePath, Path.Op.DIFFERENCE);
        }

        mCirclePath.close();
        canvas.drawPath(mCirclePath, mWavePaint);

    }


    private void startAnimator(float value) {
        if (mRatioAnimator == null) {
            mRatioAnimator = new ValueAnimator();
        }
        mRatioAnimator.cancel();
        mRatioAnimator.setFloatValues(value);
        mRatioAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator value) {

                mRatio = (float) value.getAnimatedValue();
                //球弹到位的时候,并且点击的位置变了,即点击的时候显示当前选择位置
                if (mRatio == 1f && mOldPosition != mNewPosition) {
                    if (mNewPosition >= 0 && mNewPosition < mLetters.size()) {
                        mChoosePosition = mNewPosition;
                        if (mListener != null) {
                            mListener.onLetterChange(mLetters.get(mNewPosition));
                        }
                    }
                }
                invalidate();
            }
        });
        mRatioAnimator.start();
    }


    public void setOnTouchLetterChangeListener(OnTouchLetterChangeListener listener) {
        this.mListener = listener;
    }

    public List<String> getLetters() {
        return mLetters;
    }

    public void setLetters(List<String> letters) {
        this.mLetters = letters;
        invalidate();
    }

    public interface OnTouchLetterChangeListener {
        void onLetterChange(String letter);
    }
}
适配器代码
public class UserAdapter extends BaseQuickAdapter<UserBean, BaseViewHolder> {
    private List<UserBean> userList;

    public UserAdapter(@Nullable List<UserBean> data) {
        super(R.layout.item_user, data);
        this.userList = data;
    }

    @Override
    protected void convert(@NonNull BaseViewHolder helper, UserBean item) {
        helper.setText(R.id.tv_contacts_name, item.getRealName());
        helper.setText(R.id.tv_department, item.getOrganizationName());
        helper.setText(R.id.tv_phone, item.getUserPhone());


        helper.addOnClickListener(R.id.llUserName);
        helper.addOnClickListener(R.id.ivPhone);
    }


    /**
     * 根据分类的首字母的Char ascii值获取其第一次出现该首字母的位置
     */
    public int getPositionForSection(int section) {
        for (int i = 0; i < getItemCount(); i++) {
            String sortStr = userList.get(i).getLetters();
            if (sortStr != null && !sortStr.isEmpty()) {
                char firstChar = sortStr.toUpperCase().charAt(0);
                if (firstChar == section) {
                    return i;
                }
            }
        }
        return -1;
    }
}
实例
public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding binding;

    private UserAdapter userAdapter;

    /**
     * 根据拼音来排列RecyclerView里面的数据类
     */
    private PinyinComparator mComparator;
    private LinearLayoutManager manager;
    private TitleItemDecoration mDecoration;

    private List<UserBean> contactsList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        String rqsSrc = "UserList.json";
        String rqsJson = AssetsUtil.getAssetsJson(this, rqsSrc);
        BaseBean baseBean = MyJson.fromJson(rqsJson, BaseBean.class);
        List<UserBean> userList = MyJson.fromJson(MyJson.toJson(baseBean.getData()),
                new TypeToken<ArrayList<UserBean>>() {
                }.getType());

        mComparator = new PinyinComparator();
        Collections.sort(userList, mComparator);

        manager = new LinearLayoutManager(this);
        manager.setOrientation(LinearLayoutManager.VERTICAL);
        binding.recyclerView.setLayoutManager(manager);
        userAdapter = new UserAdapter(new ArrayList<>());
        binding.recyclerView.setAdapter(userAdapter);

        contactsList = filledData(userList);
        refreshAdapter(contactsList);

        userAdapter.setOnItemChildClickListener((adapter, view, position) -> {
            UserBean item = (UserBean) adapter.getItem(position);
            int id = view.getId();
            if (id == R.id.llUserName) {
                Toast.makeText(this, "点击条目测试:Name " + item.getRealName(), Toast.LENGTH_SHORT).show();
            } else if (id == R.id.ivPhone) {
                String realName    = item.getRealName();
                String phoneNumber = item.getUserPhone();
                if (TextUtils.isEmpty(phoneNumber)) {
                    Toast.makeText(this, "联系人没有保留电话!", Toast.LENGTH_SHORT).show();
                } else {
                    new AlertDialog.Builder(this)
                            .setTitle("拨号")
                            .setMessage(realName + "\n" + phoneNumber + "\n" + "请确认要拨打的电话?")
                            .setNegativeButton(R.string.cancel, null)
                            .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    Intent intent = new Intent(Intent.ACTION_DIAL);
                                    Uri ril = Uri.parse("tel:" + phoneNumber);
                                    intent.setData(ril);
                                    startActivity(intent);
                                }
                            })
                            .show();

                }
            }
        });

        //设置右侧SideBar触摸监听
        binding.sideBar.setOnTouchLetterChangeListener(new WaveSideBar.OnTouchLetterChangeListener() {
            @Override
            public void onLetterChange(String letter) {
                //该字母首次出现的位置
                int position = userAdapter.getPositionForSection(letter.charAt(0));
                if (position != -1) {
                    manager.scrollToPositionWithOffset(position, 0);
                }
            }
        });
    }

    /**
     * 为RecyclerView填充数据
     *
     * @param date
     * @return
     */
    private List<UserBean> filledData(List<UserBean> date) {
        for (int i = 0; i < date.size(); i++) {
            //汉字转换成拼音
            String pinyin = PinyinUtils.getPingYin(date.get(i).getRealName());
            String sortString = pinyin.substring(0, 1).toUpperCase();

            // 正则表达式,判断首字母是否是英文字母
            if (sortString.matches("[A-Z]")) {
                date.get(i).setLetters(sortString.toUpperCase());
            } else {
                date.get(i).setLetters("#");
            }
        }
        return date;
    }

    private void refreshAdapter(List<UserBean> contactsListBean) {
        // 根据a-z进行排序源数据
        Collections.sort(contactsListBean, mComparator);
        userAdapter.getData().clear();
        userAdapter.replaceData(contactsListBean);
        if (mDecoration == null) {
            mDecoration = new TitleItemDecoration(this, contactsList);
            //如果add两个,那么按照先后顺序,依次渲染。
            binding.recyclerView.addItemDecoration(mDecoration);
            binding.recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
        }
    }
}

以上是大部分主要代码,由于代码比较多,只能贴出主要代码,如果有不明白的的也可以下载demo来参考,如果有积分的可以直接点击下载!

下载地址: CSDN 点击下载

如果没有积分,也想要代码的可以留言单独发,请有积分的勿骚扰!
文章写作不易请大家点赞👍、收藏📁!

总结

通过引入滑动列表索引波浪侧边栏和电话拨打功能,我们不仅提高了应用的用户体验,还为用户提供了更便捷的操作方式。这一创新设计不仅让应用更加吸引人,同时也符合现代用户对于高效、直观操作的期望。在移动应用开发中,不断尝试新的交互设计和功能整合,将是取得成功的关键之一。

无论是哪个阶段,坚持努力都是成功的关键。不要停下脚步,继续前行,即使前路崎岖,也请保持乐观和勇气。相信自己的能力,你所追求的目标定会在不久的将来实现。加油!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1282496.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NFTScan | 11.27~12.03 NFT 市场热点汇总

欢迎来到由 NFT 基础设施 NFTScan 出品的 NFT 生态热点事件每周汇总。 周期&#xff1a;2023.11.20~ 2023.11.26 NFT Hot News 01/ Web3 教育平台 Open Campus 获 Binance Labs 315 万美元投资 11 月 27 日&#xff0c;Binance Labs 已向社区主导的 Web3 教育平台 Open Campu…

github问题解决(持续更新中)

1、ssh: connect to host github.com port 22: Connection refused 从.ssh文件夹中新建文件名为config&#xff0c;内容为&#xff1a; Host github.com Hostname ssh.github.com Port 4432、解决 git 多用户提交切换问题 使用系统命令ssh创建rsa公私秘钥 C:\Users\fyp01&g…

【C语言】字符串函数strlen #strcpy #strcmp #strcat #strstr及其模拟实现

在C语言中&#xff0c;有一种特殊的数据类型&#xff0c;即字符串类型。C 并没有专门定义一个字符串类型&#xff0c;这对我们使用字符串造成了一定的麻烦。但是&#xff0c;C标准库<string.h> 中定义了各种字符串函数&#xff0c;这对于我们来说是一件值得庆幸的事情。…

【cmake】获取到某个目录下的所有子目录名

整体工程目录结构如下。现打算获取到vac目录下的所有子目录名。 cmake 实现如下: # 设定要遍历的目录&#xff0c;保存到 VAC_INCLUDE_DIR 变量 set(VAC_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/vac)# 获取到当前目录下的所有文件和目录&#xff08;以相对路径的方式&#xff09;&a…

Apache Hive(部署+SQL)

Hive架构 Hive部署 VMware虚拟机部署 一、在node1节点安装mysql数据库 二、配置Hadoop 三、下载 解压Hive 四、提供mysql Driver驱动 五、配置Hive 六、初始化元数据库 七、启动Hive(Hadoop用户) chown -R hadoop:hadoop apache-hive-3.1.3-bin hive 阿里云部…

揭秘:大厂设计师是如何制定UI风格的?

当你碰到一个新的项目或产品战略需要进行重大的改变时&#xff0c;作为UI设计师&#xff0c;你要如何重新思考产品的视觉风格&#xff1f;从何处开始&#xff1f;存在哪些重要注意点&#xff1f;今天我们有幸请到Pixso的设计师&#xff0c;他们将以出租车应用程序的风格设计过程…

基于SSM的数学竞赛网站设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

STM32使用SIM900A、SIM800C、SIM800A完成短信发送、连接onenet上传数据、拨打电话_完整教程

一、前言 本篇文章介绍SIM800C 、SIM800A、SIM900A 等等系列的模块的常用AT指令,讲解模块的使用方法,演示短信发送、拨打电话、网络连接,与服务器通信等常用案例。 如果只是用到发送短信、拨打电话、连接网络通信、这些模块的AT指令是兼容的。 文章最后贴了完整的STM32代码…

双指针算法: 快乐数 与 盛水最多的容器

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;&#x1f35f;&#x1f32f;C语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 前言 声明…

Net8 EFCore Mysql 连接

一、安装插件 Pomelo.EntityFrameworkCore.MySq (这里要选8.0.0以上版本低版本不支持.net8) 二、配置数据库连接串 appsettings.json 中配置数据库连接串 "ConnectionStrings": {"Connection": "server172.18.2.183;port3306;databasestudents;uid…

基于单片机远程温控检测系统

**单片机设计介绍&#xff0c;基于单片机远程温控检测系统&#xff08;含上位机&#xff09; 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的远程温控检测系统可以用于远程监测和控制温度&#xff0c;实现远程温度监…

【C++初阶】六、类和对象(初始化列表、static成员、友元、内部类)

相关代码gitee自取&#xff1a; C语言学习日记: 加油努力 (gitee.com) 接上期&#xff1a; 【C初阶】五、类和对象 &#xff08;日期类的完善、流运算符重载函数、const成员、“&”取地址运算符重载&#xff09;-CSDN博客 目录 ​​​​​​​一 . 初始化列表 构造函数…

【运筹优化】运筹学导论:求解线性规划问题 - 单纯形法

文章目录 一、单纯形法的实质&#xff08;几何原理&#xff09;1.1 示例的求解1.2 关键的解原理1.2.1 解原理11.2.2 解原理21.2.3 解原理31.2.4 解原理41.2.5 解原理51.2.6 解原理6 二、构建单纯形法&#xff08;代数原理&#xff09;三、单纯形法的代数形式3.1 初始化3.2 最优…

SPM/SCM 流量跟踪体系

SPM SPM&#xff08;shopping page mark&#xff0c;导购页面标记&#xff09; 是淘宝社区电商业务&#xff08;xTao&#xff09;为外部合作伙伴&#xff08;外站&#xff09;提供的跟踪引导成交效果数据的解决方案 注&#xff1a;也有解释 SPM 为超级位置模型(Super Position…

研习代码 day47 | 动态规划——子序列问题3

一、判断子序列 1.1 题目 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是"abcde&…

无需公网IP!Apache服务器本地部署与内网穿透实现公网访问

Apache服务安装配置与结合内网穿透实现公网访问 文章目录 Apache服务安装配置与结合内网穿透实现公网访问前言1.Apache服务安装配置1.1 进入官网下载安装包1.2 Apache服务配置 2.安装cpolar内网穿透2.1 注册cpolar账号2.2 下载cpolar客户端 3. 获取远程桌面公网地址3.1 登录cpo…

VPS简介:基于Amazon Ligtsail的概述

当你作为一个开发者&#xff0c;你想要开发自己的系统&#xff0c;构建自己的系统架构时&#xff0c;你会有以下两种选择&#xff1a;第一种就是亲自去挑选组件&#xff0c;例如&#xff1a;云服务器、存储、IP地址等等&#xff0c;然后再花时间自己组装起来&#xff0c;就像该…

天翼云:“百万IOPS”助推政企上云

随着数字化转型的加速&#xff0c;越来越多的企业选择了业务上云。众所周知&#xff0c;不论是政企关键/核心业务中的大型数据库、NoSQL、AI训练&#xff0c;还是互联网业务中的游戏、渲染等场景&#xff0c;对数据读写IOPS和时延有极高的要求。作为全球领先的云服务商&#xf…

如何打印社保参保凭证

西安市&#xff1a; 陕西政务服务网&#xff1a; 个人服务 珠海市&#xff1a; 广东政务服务网&#xff1a; 用户登录 | 珠海市人力资源和社会保障网上服务平台 武汉市&#xff1a; 湖北政务服务网&#xff1a; 湖北政务服务网

Android实验:绑定service实验

目录 实验目的实验内容实验要求项目结构代码实现代码解释结果展示 实验目的 充分理解Service的作用&#xff0c;与Activity之间的区别&#xff0c;掌握Service的生命周期以及对应函数&#xff0c;了解Service的主线程性质&#xff1b;掌握主线程的界面刷新的设计原则&#xff…