返回顶部
首页 > 资讯 > 精选 >Android怎么自定义双向滑动控件
  • 804
分享到

Android怎么自定义双向滑动控件

2023-06-30 05:06:37 804人浏览 独家记忆
摘要

这篇文章主要介绍“Android怎么自定义双向滑动控件”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Android怎么自定义双向滑动控件”文章能帮助大家解决问题。先看一下效果图1.SeekBarPr

这篇文章主要介绍“Android怎么自定义双向滑动控件”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Android怎么自定义双向滑动控件”文章能帮助大家解决问题。

先看一下效果图

Android怎么自定义双向滑动控件

1.SeekBarPressure工具类

public class SeekBarPressure extends View {    private static final String TAG = "SeekBarPressure";    private static final int CLICK_ON_LOW = 1;      //点击在前滑块上    private static final int CLICK_ON_HIGH = 2;     //点击在后滑块上    private static final int CLICK_IN_LOW_AREA = 3;    private static final int CLICK_IN_HIGH_AREA = 4;    private static final int CLICK_OUT_AREA = 5;    private static final int CLICK_INVaiLD = 0;        private static final int[] STATE_NORMAL = {};    private static final int[] STATE_PRESSED = {            android.R.attr.state_pressed, android.R.attr.state_window_focused,    };    private Drawable hasScrollBarBg;        //滑动条滑动后背景图    private Drawable notScrollBarBg;        //滑动条未滑动背景图    private Drawable mThumbLow;         //前滑块    private Drawable mThumbHigh;        //后滑块     private int mScollBarWidth;     //控件宽度=滑动条宽度+滑动块宽度    private int mScollBarHeight;    //滑动条高度     private int mThumbWidth;        //滑动块宽度    private int mThumbHeight;       //滑动块高度     private double mOffsetLow = 0;     //前滑块中心坐标    private double mOffsetHigh = 0;    //后滑块中心坐标    private int mDistance = 0;      //总刻度是固定距离 两边各去掉半个滑块距离     private int mThumbMarginTop = 30;   //滑动块顶部距离上边框距离,也就是距离字体顶部的距离     private int mFlag = CLICK_INVAILD;    private OnSeekBarChangeListener mBarChangeListener;      private double defaultScreenLow = 0;    //默认前滑块位置百分比    private double defaultScreenHigh = 100;  //默认后滑块位置百分比     private boolean isEdit = false;     //输入框是否正在输入     public SeekBarPressure(Context context) {        this(context, null);    }     public SeekBarPressure(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }     public SeekBarPressure(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);//        this.setBackgroundColor(Color.BLACK);         Resources resources = getResources();        notScrollBarBg = resources.getDrawable(R.color.red);        hasScrollBarBg = resources.getDrawable(R.color.blue);        mThumbLow = resources.getDrawable(R.drawable.blue);        mThumbHigh = resources.getDrawable(R.drawable.red);         mThumbLow.setState(STATE_NORMAL);        mThumbHigh.setState(STATE_NORMAL);         mScollBarWidth = notScrollBarBg.getIntrinsicWidth();        mScollBarHeight = notScrollBarBg.getIntrinsicHeight();         mThumbWidth = mThumbLow.getIntrinsicWidth();        mThumbHeight = mThumbLow.getIntrinsicHeight();     }     //默认执行,计算view的宽高,在onDraw()之前    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int width = measureWidth(widthMeasureSpec);//        int height = measureHeight(heightMeasureSpec);        mScollBarWidth = width;        mOffsetHigh = width - mThumbWidth / 2;        mOffsetLow = mThumbWidth / 2;        mDistance = width - mThumbWidth;         mOffsetLow = formatDouble(defaultScreenLow / 100 * (mDistance ))+ mThumbWidth / 2;        mOffsetHigh = formatDouble(defaultScreenHigh / 100 * (mDistance)) + mThumbWidth / 2;        setMeasuredDimension(width, mThumbHeight + mThumbMarginTop+2);    }      private int measureWidth(int measureSpec) {        int specMode = MeasureSpec.getMode(measureSpec);        int specSize = MeasureSpec.getSize(measureSpec);        //wrap_content        if (specMode == MeasureSpec.AT_MOST) {        }        //fill_parent或者精确值        else if (specMode == MeasureSpec.EXACTLY) {        }         return specSize;    }     private int measureHeight(int measureSpec) {        int specMode = MeasureSpec.getMode(measureSpec);        int specSize = MeasureSpec.getSize(measureSpec);        int defaultHeight = 100;        //wrap_content        if (specMode == MeasureSpec.AT_MOST) {        }        //fill_parent或者精确值        else if (specMode == MeasureSpec.EXACTLY) {            defaultHeight = specSize;        }         return defaultHeight;    }     protected void onLayout(boolean changed, int l, int t, int r, int b) {        super.onLayout(changed, l, t, r, b);    }     protected void onDraw(canvas canvas) {        super.onDraw(canvas); //        Paint text_Paint = new Paint();//        text_Paint.setTextAlign(Paint.Align.CENTER);//        text_Paint.setColor(Color.RED);//        text_Paint.setTextSize(50);         int aaa = mThumbMarginTop + mThumbHeight / 2 - mScollBarHeight / 2;        int bbb = aaa + mScollBarHeight;         //白色,不会动        notScrollBarBg.setBounds(mThumbWidth / 2, aaa, mScollBarWidth - mThumbWidth / 2, bbb);        notScrollBarBg.draw(canvas);         //蓝色,中间部分会动        hasScrollBarBg.setBounds((int)mOffsetLow, aaa, (int)mOffsetHigh, bbb);        hasScrollBarBg.draw(canvas);         //前滑块        mThumbLow.setBounds((int)(mOffsetLow - mThumbWidth / 2), mThumbMarginTop, (int)(mOffsetLow + mThumbWidth / 2), mThumbHeight + mThumbMarginTop);        mThumbLow.draw(canvas);         //后滑块        mThumbHigh.setBounds((int)(mOffsetHigh - mThumbWidth / 2), mThumbMarginTop, (int)(mOffsetHigh + mThumbWidth / 2), mThumbHeight + mThumbMarginTop);        mThumbHigh.draw(canvas);         double progressLow = formatDouble((mOffsetLow - mThumbWidth / 2) * 100 / mDistance);        double progresshigh = formatDouble((mOffsetHigh - mThumbWidth / 2) * 100 / mDistance);//            Log.d(TAG, "onDraw-->mOffsetLow: " + mOffsetLow + "  mOffsetHigh: " + mOffsetHigh   + "  progressLow: " + progressLow + "  progressHigh: " + progressHigh);       // canvas.drawText((int) progressLow + "", (int)mOffsetLow - 2 - 2, 15, text_Paint);       // canvas.drawText((int) progressHigh + "", (int)mOffsetHigh - 2, 15, text_Paint);         if (mBarChangeListener != null) {            if (!isEdit) {                mBarChangeListener.onProgressChanged(this, progressLow, progressHigh);            }         }    }     @Override    public boolean onTouchEvent(MotionEvent e) {        //按下        if (e.getAction() == MotionEvent.ACTION_DOWN) {            if (mBarChangeListener != null) {                mBarChangeListener.onProgressBefore();                isEdit = false;            }            mFlag = getAreaFlag(e);//            Log.d(TAG, "e.getX: " + e.getX() + "mFlag: " + mFlag);//            Log.d("ACTION_DOWN", "------------------");            if (mFlag == CLICK_ON_LOW) {                mThumbLow.setState(STATE_PRESSED);            } else if (mFlag == CLICK_ON_HIGH) {                mThumbHigh.setState(STATE_PRESSED);            } else if (mFlag == CLICK_IN_LOW_AREA) {                mThumbLow.setState(STATE_PRESSED);                //如果点击0-mThumbWidth/2坐标                if (e.getX() < 0 || e.getX() <= mThumbWidth/2) {                    mOffsetLow = mThumbWidth/2;                } else if (e.getX() > mScollBarWidth - mThumbWidth/2) {//                    mOffsetLow = mDistance - mDuration;                    mOffsetLow = mThumbWidth/2 + mDistance;                } else {                    mOffsetLow = formatDouble(e.getX());//                    if (mOffsetHigh<= mOffsetLow) {//                        mOffsetHigh = (mOffsetLow + mDuration <= mDistance) ? (mOffsetLow + mDuration)//                                : mDistance;//                        mOffsetLow = mOffsetHigh - mDuration;//                    }                }            } else if (mFlag == CLICK_IN_HIGH_AREA) {                mThumbHigh.setState(STATE_PRESSED);//                if (e.getX() < mDuration) {//                    mOffsetHigh = mDuration;//                    mOffsetLow = mOffsetHigh - mDuration;//                } else if (e.getX() >= mScollBarWidth - mThumbWidth/2) {//                    mOffsetHigh = mDistance + mThumbWidth/2;                if(e.getX() >= mScollBarWidth - mThumbWidth/2) {                    mOffsetHigh = mDistance + mThumbWidth/2;                } else {                    mOffsetHigh = formatDouble(e.getX());//                    if (mOffsetHigh <= mOffsetLow) {//                        mOffsetLow = (mOffsetHigh - mDuration >= 0) ? (mOffsetHigh - mDuration) : 0;//                        mOffsetHigh = mOffsetLow + mDuration;//                    }                }            }            //设置进度条            refresh();             //移动move        } else if (e.getAction() == MotionEvent.ACTION_MOVE) {//            Log.d("ACTION_MOVE", "------------------");            if (mFlag == CLICK_ON_LOW) {                if (e.getX() < 0 || e.getX() <= mThumbWidth/2) {                    mOffsetLow = mThumbWidth/2;                } else if (e.getX() >= mScollBarWidth - mThumbWidth/2) {                    mOffsetLow = mThumbWidth/2 + mDistance;                    mOffsetHigh = mOffsetLow;                } else {                    mOffsetLow = formatDouble(e.getX());                    if (mOffsetHigh - mOffsetLow <= 0) {                        mOffsetHigh = (mOffsetLow <= mDistance+mThumbWidth/2) ? (mOffsetLow) : (mDistance+mThumbWidth/2);                    }                }            } else if (mFlag == CLICK_ON_HIGH) {                if (e.getX() <  mThumbWidth/2) {                    mOffsetHigh = mThumbWidth/2;                    mOffsetLow = mThumbWidth/2;                } else if (e.getX() > mScollBarWidth - mThumbWidth/2) {                    mOffsetHigh = mThumbWidth/2 + mDistance;                } else {                    mOffsetHigh = formatDouble(e.getX());                    if (mOffsetHigh - mOffsetLow <= 0) {                        mOffsetLow = (mOffsetHigh >= mThumbWidth/2) ? (mOffsetHigh) : mThumbWidth/2;                    }                }            }            //设置进度条            refresh();            //抬起        } else if (e.getAction() == MotionEvent.ACTION_UP) {//            Log.d("ACTION_UP", "------------------");            mThumbLow.setState(STATE_NORMAL);            mThumbHigh.setState(STATE_NORMAL);             if (mBarChangeListener != null) {                mBarChangeListener.onProgressAfter();            }            //这两个for循环 是用来自动对齐刻度的,注释后,就可以自由滑动到任意位置//            for (int i = 0; i < money.length; i++) {//                 if(Math.abs(mOffsetLow-i* ((mScollBarWidth-mThumbWidth)/ (money.length-1)))<=(mScollBarWidth-mThumbWidth)/(money.length-1)/2){//                     mprogressLow=i;//                     mOffsetLow =i* ((mScollBarWidth-mThumbWidth)/(money.length-1));//                     invalidate();//                     break;//                }//            }////            for (int i = 0; i < money.length; i++) {//                  if(Math.abs(mOffsetHigh-i* ((mScollBarWidth-mThumbWidth)/(money.length-1) ))<(mScollBarWidth-mThumbWidth)/(money.length-1)/2){//                      mprogressHigh=i;//                       mOffsetHigh =i* ((mScollBarWidth-mThumbWidth)/(money.length-1));//                       invalidate();//                       break;//                }//            }        }        return true;    }     public int getAreaFlag(MotionEvent e) {         int top = mThumbMarginTop;        int bottom = mThumbHeight + mThumbMarginTop;        if (e.getY() >= top && e.getY() <= bottom && e.getX() >= (mOffsetLow - mThumbWidth / 2) && e.getX() <= mOffsetLow + mThumbWidth / 2) {            return CLICK_ON_LOW;        } else if (e.getY() >= top && e.getY() <= bottom && e.getX() >= (mOffsetHigh - mThumbWidth / 2) && e.getX() <= (mOffsetHigh + mThumbWidth / 2)) {            return CLICK_ON_HIGH;        } else if (e.getY() >= top                && e.getY() <= bottom                && ((e.getX() >= 0 && e.getX() < (mOffsetLow - mThumbWidth / 2)) || ((e.getX() > (mOffsetLow + mThumbWidth / 2))                && e.getX() <= ((double) mOffsetHigh + mOffsetLow) / 2))) {            return CLICK_IN_LOW_AREA;        } else if (e.getY() >= top                && e.getY() <= bottom                && (((e.getX() > ((double) mOffsetHigh + mOffsetLow) / 2) && e.getX() < (mOffsetHigh - mThumbWidth / 2)) || (e                .getX() > (mOffsetHigh + mThumbWidth/2) && e.getX() <= mScollBarWidth))) {            return CLICK_IN_HIGH_AREA;        } else if (!(e.getX() >= 0 && e.getX() <= mScollBarWidth && e.getY() >= top && e.getY() <= bottom)) {            return CLICK_OUT_AREA;        } else {            return CLICK_INVAILD;        }    }     //更新滑块    private void refresh() {        invalidate();    }     //设置前滑块的值    public void setProgressLow(double  progressLow) {        this.defaultScreenLow = progressLow;        mOffsetLow = formatDouble(progressLow / 100 * (mDistance ))+ mThumbWidth / 2;        isEdit = true;        refresh();    }     //设置后滑块的值    public void setProgressHigh(double  progressHigh) {        this.defaultScreenHigh = progressHigh;        mOffsetHigh = formatDouble(progressHigh / 100 * (mDistance)) + mThumbWidth / 2;        isEdit = true;        refresh();    }     public void setOnSeekBarChangeListener(OnSeekBarChangeListener mListener) {        this.mBarChangeListener = mListener;    }     //回调函数,在滑动时实时调用,改变输入框的值    public interface OnSeekBarChangeListener {        //滑动前        public void onProgressBefore();         //滑动时        public void onProgressChanged(SeekBarPressure seekBar, double progressLow,                                      double progressHigh);         //滑动后        public void onProgressAfter();    }      public static double formatDouble(double pDouble) {        BigDecimal bd = new BigDecimal(pDouble);        BigDecimal bd1 = bd.setScale(2, BigDecimal.ROUND_HALF_UP);        pDouble = bd1.doubleValue();        return pDouble;    } }

2.activity_main.xml 布局

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="Http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context=".MainActivity"    android:background="#000"    android:orientation="vertical">     <com.yjjk.doubleseekbarsss.SeekBarPressure        android:id="@+id/seekBar_tg2"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_alignParentTop="true"        android:layout_marginBottom="10dp"        android:layout_marginLeft="10dp"        android:layout_marginRight="10dp" />    <TextView        android:id="@+id/editTexts_min"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:textSize="15sp"        android:textColor="#fff"></TextView>    <TextView        android:id="@+id/editTexts_max"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:textSize="15sp"        android:textColor="#fff"></TextView>  </LinearLayout>

3.MainActivity

public class MainActivity extends AppCompatActivity {     private boolean isScreen;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);         //id        final SeekBarPressure seekBarPressures = findViewById(R.id.seekBar_tg2);        final TextView editTexts_min = findViewById(R.id.editTexts_min);        final TextView editTexts_max = findViewById(R.id.editTexts_max);         seekBarPressures.setOnSeekBarChangeListener(new SeekBarPressure.OnSeekBarChangeListener() {            @Override            public void onProgressBefore() {                isScreen = true;            }             @Override            public void onProgressChanged(SeekBarPressure seekBar, double progressLow, double progressHigh) {                 editTexts_min.setText((int) progressLow + "");                editTexts_max.setText((int) progressHigh + "");                 //获取SharedPreferences对象                SharedPreferences sharedPre=getSharedPreferences("config", MODE_PRIVATE);                 //获取Editor对象                SharedPreferences.Editor edit = sharedPre.edit();                edit.clear();                 //设置参数                edit.putString("progressHigh", String.valueOf(progressHigh));                edit.putString("progressLow", String.valueOf(progressLow));                 //提交                edit.commit();             }             @Override            public void onProgressAfter() {                isScreen = false;            }        });          //取值        SharedPreferences sharedPre=getSharedPreferences("config",MODE_PRIVATE);         String progressHighs = sharedPre.getString("progressHigh", "");        String progressLows = sharedPre.getString("progressLow", "");         if (progressHighs.length()!=0){            seekBarPressures.setProgressHigh(Double.parseDouble(progressHighs));            seekBarPressures.setProgressLow(Double.parseDouble(progressLows));             editTexts_max.setText(progressHighs);            editTexts_min.setText(progressLows);        }else {            seekBarPressures.setProgressLow(30);            seekBarPressures.setProgressHigh(70);             editTexts_min.setText("最小值:"+30);            editTexts_max.setText("最大值:"+70);        }    }}

关于“Android怎么自定义双向滑动控件”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注编程网精选频道,小编每天都会为大家更新不同的知识点。

--结束END--

本文标题: Android怎么自定义双向滑动控件

本文链接: https://lsjlt.com/news/327547.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Android自定义双向滑动控件
    本文实例为大家分享了Android自定义双向滑动控件的具体代码,供大家参考,具体内容如下 先看一下效果图 1.SeekBarPressure工具类 public class See...
    99+
    2024-04-02
  • Android怎么自定义双向滑动控件
    这篇文章主要介绍“Android怎么自定义双向滑动控件”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Android怎么自定义双向滑动控件”文章能帮助大家解决问题。先看一下效果图1.SeekBarPr...
    99+
    2023-06-30
  • Android开发自定义双向SeekBar拖动条控件
    目录目标:双向拖动的自定义View实现步骤自定义属性获取确定自定义view尺寸绘制相关的内容部分滑动事件处理目标:双向拖动的自定义View 国际惯例先预览后实现 我们要实现的就是一...
    99+
    2024-04-02
  • Android自定义View实现随手势滑动控件
    本文控件为大家分享了Android随手势滑动控件的具体代码,供大家参考,具体内容如下 1.新建自定义控件类:MyView public class MyView extend...
    99+
    2022-06-06
    view 手势 Android
  • Android自定义控件实现滑动开关效果
    自定义开关控件   Android自定义控件一般有三种方式 1、继承Android固有的控件,在Android原生控件的基础上,进行添加功能和逻辑。 2、继承V...
    99+
    2022-06-06
    开关 Android
  • Android自定义滑动解锁控件使用详解
    最近的项目里用到了,在网上找不到合适的,于是自己写了个简单的,带回弹效果:可以自定义的属性有:<!-- 滑动解锁控件 xml配置属性 --><declare-styleable name="SlideToUnlockVie...
    99+
    2023-05-30
    android 滑动解锁 roi
  • Android自定义滑动接听电话控件组实例
    本文根据组件开发思想,首先介绍android自定义控件,然后将自定义的控件封装为jar包。最为实现滑动接听电话控件组。 一、目录结构 二、运行效果 三、代码实现 首...
    99+
    2022-06-06
    电话 Android
  • Android中怎么自定义双向进度条
    本篇文章给大家分享的是有关Android中怎么自定义双向进度条,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。注释基本上就把原理说明了一下。package util;i...
    99+
    2023-05-30
    android
  • Android自定义控件实现简单滑动开关效果
    本文实例为大家分享了Android自定义控件实现简单滑动开关的具体代码,供大家参考,具体内容如下 ToggleButton 滑动开关 项目概述 滑动开关是一个纯粹的自定义控件,上面的...
    99+
    2024-04-02
  • Android自定义view实现滑动解锁九宫格控件
    目录前言需求效果图前言 上一篇文章用贝塞尔曲线画了一个看起来不错的小红点功能,技术上没什么难度,主要就是数学上的计算。这篇文章也差不多,模仿了一个常用的滑动解锁的九宫格控件。 需求 ...
    99+
    2023-02-09
    Android滑动解锁九宫格 Android滑动解锁 Android九宫格控件
  • Android怎么自定义View实现竖向滑动回弹效果
    这篇文章主要介绍“Android怎么自定义View实现竖向滑动回弹效果”,在日常操作中,相信很多人在Android怎么自定义View实现竖向滑动回弹效果问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Andro...
    99+
    2023-06-30
  • Android自定义控件实现可左右滑动的导航条
    先上效果图: 这个控件其实算是比较轻量级的,相信不少小伙伴都能做出来。因为项目中遇到了一些特殊的定制要求,所以就自己写了一个,这里放出来。  首先来分析下这个控件的...
    99+
    2022-06-06
    导航条 Android
  • 使用Android自定义控件实现滑动解锁九宫格
    本文概述:  滑动解锁九宫格的分析: 1、需要自定义控件; 2、需要重写事件onTouchEvent(); 3、需要给九个点设置序号和坐标,这里用Map类就行;...
    99+
    2022-06-06
    解锁 Android
  • Android自定义控件之自定义组合控件(三)
    前言: 前两篇介绍了自定义控件的基础原理Android自定义控件基本原理详解(一)、Android自定义控件之自定义属性(二)。今天重点介绍一下如何通过自定义组合控件来提高布...
    99+
    2022-06-06
    Android
  • Android自定义View实现竖向滑动回弹效果
    本文实例为大家分享了Android自定义View实现滑动回弹的具体代码,供大家参考,具体内容如下 前言 Android 页面滑动的时候的回弹效果 一、关键代码 public clas...
    99+
    2024-04-02
  • Android中怎么自定义Progress控件
    Android中怎么自定义Progress控件,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。主要就是需求就是椭圆进度,百分比跟随渐变背景,这样一想其实就是一个布局,然后控制...
    99+
    2023-05-31
    android progress
  • android怎么自定义组合控件
    要自定义一个组合控件,你可以按照以下步骤进行:1. 创建一个新的类,继承自现有的Android控件类,例如LinearLayout或...
    99+
    2023-08-09
    android
  • android怎么自定义开关控件
    要自定义开关控件,可以使用以下步骤:1. 创建一个自定义的开关控件类,继承自Switch或CompoundButton类。2. 在自...
    99+
    2023-08-16
    android
  • Android自定义viewgroup快速滑动(4)
    上一篇文章自定义viewgroup(3)地址://www.jb51.net/article/100618.htm 代码: package com.example.libing...
    99+
    2022-06-06
    Android
  • Android自定义控件实现方向盘效果
    在很多开发中,为了界面更加的友好,在自定义View的基础上,开发者会开发出各种各样的自定义控件来满足实际开发需要,其中有一种”方向盘”的控件在实际开发中非常常见,便于用户进行一...
    99+
    2022-06-06
    Android
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作