0

0

如何实现自定义圆环状时间滚轮

零到壹度

零到壹度

发布时间:2018-04-09 10:44:39

|

1752人浏览过

|

来源于php中文网

原创


本篇文章给大家分享的内容是如何实现自定义圆环状时间滚轮,有着一定的参考价值,有需要的朋友可以参考一下

先看一下最终效果:

这里写图片描述

Cutout.Pro抠图
Cutout.Pro抠图

AI批量抠图去背景

下载

实现

功能简介: 一年以内的任意天和week是可以对应起来的,其他的日与月不限制年
实现原理:利用一个自定义的滚轮来写日周月相互对应的逻辑部分
自定义view的源码如下:

package com.lwyy.wheel.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;


import com.lwyy.wheel.R;

import java.util.ArrayList;
import java.util.List;

 /**
 * Created by ll on 2017/9/25.
 */
 
 public class CircleWheelView extends View {
    private static final String TAG = CircleWheelView.class.getSimpleName();    
    private int mItemAlign;    
    public static final int ALIGN_CENTER = 0, ALIGN_LEFT = 1, ALIGN_RIGHT = 2;    
    private Paint mPaint;    
    private OnItemSelectedListener mOnItemSelectedListener;    
    private int mTextMaxWidth, mTextMaxHeight;
        
    private Rect mRectDrawn;    
    private int mDrawnCenterX, mDrawnCenterY;                                                  //滚轮选择器绘制中心坐标
    private int mWheelCenterX, mWheelCenterY;                                                  //滚轮选择器中心坐标
    private int mItemTextColor, mSelectedItemTextColor;                                      //数据项文本颜色以及被选中的数据项文本颜色
    private int mItemHeight;    
    private float mItemTextSize;    
    
    private int mLastPointY;                                                                      //用户手指上一次触摸事件发生时事件Y坐标
    private float moveY;    
    
    private List mData = new ArrayList();    
    private List mDataCC = new ArrayList();    
    private int mTurnToCenterX;    
    
    private int mCurrentItemPosition;    
    private int mDefaultHalfNum = 7, mDefaultVisibleNum = 13;                                         //设置当前view默认可见的item个数
    private int mVisibleHalfNum, mVisibleCount;                                                 //视图区域内的展示的item个数
    private int mDataSize;    
    
    private boolean isCyclic;                                                                    //数据是否循环展示  针对list的数目小于mDefaultVisibleNum
    private boolean isLoopDisplay;                                                              //所有的数据是否是球形展示,即永远的头尾衔接,如果isCyclic是true,则该值也是true

    private float[] rates = null;    
    private float[] textSizeRates = null;    
    
    public CircleWheelView(Context context) {    
        this(context, null);
    }    
    
    public CircleWheelView(Context context, AttributeSet attrs) {    
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleWheelView);
        mItemTextSize = a.getDimensionPixelSize(R.styleable.CircleWheelView_wheel_item_text_size,
                getResources().getDimensionPixelSize(R.dimen.WheelItemTextSize_Default));
        mItemAlign = a.getInt(R.styleable.CircleWheelView_wheel_item_align, ALIGN_CENTER);
        mItemTextColor = a.getColor(R.styleable.CircleWheelView_wheel_item_text_color, 0xFF888888);
        mSelectedItemTextColor = a.getColor(R.styleable.CircleWheelView_wheel_selected_item_text_color, 0xFF888899);
        mTurnToCenterX = a.getInt(R.styleable.CircleWheelView_wheel_turn_to_centerx, 0);             //转向中间,偏移的距离
        isCyclic = a.getBoolean(R.styleable.CircleWheelView_wheel_cyclic, false);
        isLoopDisplay = a.getBoolean(R.styleable.CircleWheelView_wheel_loop_display, false);
        a.recycle();

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.LINEAR_TEXT_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mRectDrawn = new Rect();
    }    
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {   
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);        
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);        
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);        
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);        
        
        // 计算原始内容尺寸
        int resultWidth = mTextMaxWidth;        
        int resultHeight = mTextMaxHeight * mVisibleCount;        
        
        // 考虑内边距对尺寸的影响
        resultWidth += getPaddingLeft() + getPaddingRight();
        resultHeight += getPaddingTop();        
        
        // 考虑父容器对尺寸的影响
        resultWidth = measureSize(modeWidth, sizeWidth, resultWidth);
        resultHeight = measureSize(modeHeight, sizeHeight, resultHeight);
        setMeasuredDimension(resultWidth, resultHeight);
    }    
    
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {    
        super.onSizeChanged(w, h, oldw, oldh);        
        // 获取内容区域中心坐标
        mRectDrawn.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(),
                getHeight() - getPaddingBottom());
        mWheelCenterX = mRectDrawn.centerX();
        mWheelCenterY = mRectDrawn.centerY();
        computeDrawnCenter();
    }    
    
    @Override
    protected void onDraw(Canvas canvas) {    
        if (mDataCC.size() > 0) {
            mPaint.setStrokeWidth(1);
            mPaint.setSubpixelText(true);                                                               //设置该项为true,将有助于文本在LCD屏幕上的显示效果
            canvas.save();            
            int indexBottom = 0;            
            float distanceX = 0, mDrawnItemCenterY = 0;            
            int endIndex = isCyclic ? 13 : Math.min(mDefaultVisibleNum, mDataCC.size());            
            if (endIndex > mData.size())
                endIndex = mData.size();            
            for (int i = 0; i < endIndex; i++) {        
                if (i == 0) mPaint.setColor(mSelectedItemTextColor);                
                else mPaint.setColor(mItemTextColor);                
                if (isCyclic) {          
                        if (i < mVisibleHalfNum)
                        indexBottom = i;                    
                else
                        indexBottom = i - mVisibleHalfNum + 1;                    
                if (i < mVisibleHalfNum)
                        mDrawnItemCenterY = (int) (mDrawnCenterY - (mItemHeight * rates[indexBottom]));                    
                else
                        mDrawnItemCenterY = (int) (mDrawnCenterY + (mItemHeight * rates[indexBottom]));                    
                if (i == endIndex - 1)
                        mDrawnItemCenterY = (int) (mDrawnCenterY + (mItemHeight * rates[indexBottom]) - 10);
                } else if (mData.size() < 13) {                    if (i < mVisibleHalfNum + 1)
                        indexBottom = i;                    else
                        indexBottom = i - mVisibleHalfNum;                    if (i < mVisibleHalfNum + 1)
                        mDrawnItemCenterY = (int) (mDrawnCenterY - (mItemHeight * rates[indexBottom]));                    else
                        mDrawnItemCenterY = (int) (mDrawnCenterY + (mItemHeight * rates[indexBottom]));                    if (i == endIndex - 1)
                        mDrawnItemCenterY = (int) (mDrawnCenterY + (mItemHeight * rates[indexBottom]) - 10);//                    if (i < mVisibleHalfNum)//                        indexBottom = i;//                    else//                        indexBottom = i - mVisibleHalfNum + 1;////                    if (i < mVisibleHalfNum)//                        mDrawnItemCenterY = (int) (mDrawnCenterY - (mItemHeight * rates[indexBottom]));//                    else//                        mDrawnItemCenterY = (int) (mDrawnCenterY + (mItemHeight * rates[indexBottom]));//                    if (i == endIndex - 1)//                        mDrawnItemCenterY = (int) (mDrawnCenterY + (mItemHeight * rates[indexBottom]) - 10);
                } else {          
                   if (i < mVisibleHalfNum)
                        indexBottom = i;                    
                   else
                        indexBottom = i - mVisibleHalfNum + 1;
  //                    LogUtil.e(TAG, "indexBottom:" + indexBottom + ",mVisibleHalfNum:" + mVisibleHalfNum + ",i:" + i);
                    if (indexBottom >= mDefaultHalfNum)                        
                        continue;                    
                    if (i < mVisibleHalfNum)
                        mDrawnItemCenterY = (int) (mDrawnCenterY - (mItemHeight * rates[indexBottom]));                    else
                        mDrawnItemCenterY = (int) (mDrawnCenterY + (mItemHeight * rates[indexBottom]));                    if (i == endIndex - 1)
                        mDrawnItemCenterY = (int) (mDrawnCenterY + (mItemHeight * rates[indexBottom]) - 10);
                }


                mPaint.setTextSize(mItemTextSize * textSizeRates[indexBottom]);
                paintText(canvas, distanceX, rates, mDrawnItemCenterY, indexBottom, i);
            }
        }
    }    
    
    private void paintText(Canvas canvas, float distanceX, float[] rates, float drawnCenterY, int indexBottom, int i) {   
         float mDistanceSubX = mDrawnCenterX - mTurnToCenterX * rates[indexBottom] * rates[indexBottom];        
         float mDistanceAddX = mDrawnCenterX + mTurnToCenterX * rates[indexBottom] * rates[indexBottom];        
         switch (mItemAlign) {      
           case ALIGN_CENTER:
                distanceX = mDrawnCenterX;                
                break;            
           case ALIGN_LEFT:
                distanceX = mData.get(i).toString().length() <= 1 ?
                        mDistanceSubX + 4 * (mVisibleHalfNum - indexBottom) : mDistanceSubX;                
                break;            
           case ALIGN_RIGHT:
                distanceX = mData.get(i).toString().length() <= 1 ?
                        mDistanceAddX - 4 * (mVisibleHalfNum - indexBottom) : mDistanceAddX;                
                break;
        }
        String text = "";    
            if (!isLoopDisplay && !isCyclic && mDataSize > mDefaultHalfNum) {       
              if (mCurrentItemPosition < mDefaultHalfNum - 1 && (i >= mDefaultHalfNum || i <= mCurrentItemPosition)) {
                text = String.valueOf(mData.get(i));
            } else if (mCurrentItemPosition > mDefaultHalfNum - 1 && i < mDefaultHalfNum + (mDataSize - mCurrentItemPosition) - 1)
                text = String.valueOf(mData.get(i));            
            else if (mCurrentItemPosition == mDefaultHalfNum - 1)
                text = String.valueOf(mData.get(i));
        } else if (isCyclic && !isLoopDisplay) {        
             if (mCurrentItemPosition < mDefaultHalfNum - 1 && ((i >= mDefaultHalfNum && i < mDataCC.size() - mCurrentItemPosition - 1 + mDefaultHalfNum) || i <= mCurrentItemPosition)) {
                text = String.valueOf(mData.get(i));
            } else if (mCurrentItemPosition > mDefaultHalfNum - 1 && i < mDefaultHalfNum + (mDataCC.size() - mCurrentItemPosition) - 1)
                text = String.valueOf(mData.get(i));            else if (mCurrentItemPosition == mDefaultHalfNum - 1 && i < mDataCC.size())
                text = String.valueOf(mData.get(i));
        } else {
            text = String.valueOf(mData.get(i));
       //            LogUtil.i(TAG, "text:" + mData.get(i) + ",i:" + i);
        }
        canvas.drawText(text, distanceX, drawnCenterY, mPaint);
    }    
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {    
        switch (event.getAction()) {     
            case MotionEvent.ACTION_DOWN:
                mLastPointY = (int) event.getY();                
                break;            
            case MotionEvent.ACTION_MOVE:
                moveY = event.getY() - mLastPointY;                
                if (Math.abs(moveY) < 1) break;                
                if (mDataCC.size() < mDefaultHalfNum)                    
                    break;                
                if (!isLoopDisplay) {         
                 if (mCurrentItemPosition == 0 && moveY > 0)                        
                   break;                    
                if (isCyclic && mDataCC.size() < mDefaultVisibleNum && mCurrentItemPosition == mDataCC.size() - 1 && moveY < 0)                        
                   break;                    
                else if (mCurrentItemPosition == mDataSize - 1 && moveY < 0)                        
                  break;
                }

                changeMoveItemPosition(event, mItemHeight * 3 / 2);                
                break;            
             case MotionEvent.ACTION_UP:                
                if (mDataCC.size() < mDefaultHalfNum) {
                    changeMoveItemPosition(event, mItemHeight / 2);
                }
                
                if (null != mOnItemSelectedListener && mCurrentItemPosition < mDataCC.size() && mDataCC.size() > 0 && mCurrentItemPosition >= 0)
                    mOnItemSelectedListener.onItemSelected(this, mDataCC.get(mCurrentItemPosition), mCurrentItemPosition);                break;
        }        
        return true;
    }    
    
    private void changeMoveItemPosition(MotionEvent event, int moveHeight) {    
        if (Math.abs(moveY) > moveHeight) {
            mLastPointY = (int) event.getY();            
            if (moveY > 0)
                mCurrentItemPosition--;            
            else
                mCurrentItemPosition++;            
                
             if (mCurrentItemPosition > mDataCC.size() - 1)
                mCurrentItemPosition -= mDataCC.size();            
             if (mCurrentItemPosition < 0)
                mCurrentItemPosition += mDataCC.size();            
             if (mCurrentItemPosition >= 0 && mCurrentItemPosition < mDataCC.size())
                setSelectedItemPosition(mDataCC, mCurrentItemPosition);
        }
    }    
    
    private void computeTextSize() {
        mTextMaxWidth = mTextMaxHeight = 0;        
        for (Object obj : mData) {
            String text = String.valueOf(obj);            
            int width = (int) mPaint.measureText(text);
            mTextMaxWidth = Math.max(mTextMaxWidth, width);
        }
        Paint.FontMetrics metrics = mPaint.getFontMetrics();
        mTextMaxHeight = (int) (metrics.bottom - metrics.top);
    }    
    
    private int measureSize(int mode, int sizeExpect, int sizeActual) {     
       int realSize;        if (mode == MeasureSpec.EXACTLY) {
            realSize = sizeExpect;
        } else {
            realSize = sizeActual;            if (mode == MeasureSpec.AT_MOST)
                realSize = Math.min(realSize, sizeExpect);
        }        return realSize;
    }    
    
    //固定的等比高度
    private void computeDrawnCenter() {   
        int num = 7;
        mItemHeight = mRectDrawn.height() / (num * 2 - 2);
        mPaint.setTextAlign(Paint.Align.CENTER);
        mDrawnCenterX = mWheelCenterX;
        mDrawnCenterY = mWheelCenterY + mItemHeight / num;
    }    
    
    public void updateVisibleItemCount(int num) {    
        if (isCyclic) {
            mVisibleCount = mDefaultVisibleNum = 13;
            mVisibleHalfNum = mDefaultHalfNum = 7;
        } else {       
            if (num >= mDefaultHalfNum)
                mVisibleHalfNum = mDefaultHalfNum;            
            else {
                mVisibleHalfNum = num % 2 == 1 ? num / 2 + 1 : num / 2;
            }
        }
        
   //        LogUtil.e(TAG, "mVisibleHalfNum:" + mVisibleHalfNum);
        mVisibleCount = Math.min(mDefaultVisibleNum, num);
        mDataSize = isCyclic ? mDefaultVisibleNum : num;
    }    
    
    public void setData(List mData, int pos) {
        setData(mData, pos, true);
    }    
    
    public void setData(List mData, int pos, boolean isLoopDisplay) {
        setData(mData, pos, isLoopDisplay, false, 13);
    }    
    
    public void setData(List mData, int pos, boolean isLoopDisplay, boolean isCycle) {
        setData(mData, pos, isLoopDisplay, isCycle, 13);
    }    
    
    
    /**
     * @param mData          传入的展示列表数据
     * @param pos            当前view的中间item展示的内容
     * @param isLoopDisplay  所有的数据是否是球形展示,即永远的头尾衔接,即item由mDataSize - 1下滑变为0,默认true
     * @param isCycle        数据是否循环展示  针对list的数目小于mDefaultVisibleNum. 默认false
     * @param visibleShowNum 当前界面展示的item个数
     */
    public void setData(List mData, int pos, boolean isLoopDisplay, boolean isCycle, int visibleShowNum) {    
        if (mData.size() <= 0 || pos < 0 || pos >= mData.size())         
           return;        
        if (visibleShowNum % 2 == 1)
            visibleShowNum++;        
        if (mData.size() > 12 && isCycle)
            isCycle = false;        
        else if (mData.size() < 13 && !isCycle && !isLoopDisplay) {
            isCycle = true;
        }

        mDefaultVisibleNum = mData.size() < 13 && !isCycle ? mData.size() : 13;
        mDefaultHalfNum = mDefaultVisibleNum < 13 ? mDefaultVisibleNum / 2 : 7;        
        this.isLoopDisplay = isLoopDisplay;        
        this.isCyclic = isCycle;        
        
        if (this.mDataCC.size() > 0)            
        this.mDataCC.clear();        
        this.mDataCC.addAll(mData);        
        this.mCurrentItemPosition = pos;

        updateVisibleItemCount(mData.size());
        setSelectedItemPosition(mData, pos);
        updateRates();
        computeTextSize();
        requestLayout();
        invalidate();
    }    
    
    public void updateDataPos(int pos) {    
        if (pos < 0)            
            return;        
        if (pos >= mDataCC.size())
            pos = mDataCC.size() / 2 - 1;        
        this.mCurrentItemPosition = pos;
        setSelectedItemPosition(mDataCC, pos);
        invalidate();
    }    
    
    private void updateRates() {    
        int num = 7;        
        if (null == rates)
            rates = new float[num];        
        if (null == textSizeRates)
            textSizeRates = new float[num];        
            
        float rate = 0.0f;        
        for (int i = 0; i < num; i++) {      
              if (i > 0) {
                rate = (3 * (11 * i - (i - 1) * (i - 1))) / 22f;                
              if (i == num - 1)
                    rate = (3 * (11 * i - (i - 1) * (i - 1)) + 4) / 22f;
            }
            rates[i] = rate;
            textSizeRates[i] = ((18 - 7 * i / 3) / 13f);
        }
    }    
    
    public void setItemAlign(int align) {     
        this.mItemAlign = align;
        invalidate();
    }    
    
    public void setTurnToCenter(int turnToCenterX) {    
       this.mTurnToCenterX = turnToCenterX;
        requestLayout();
        invalidate();
    }    
    
    public void setSelectedItemPosition(List mDatas, int centerPos) {   
         if (mData.size() > 0)
            mData.clear();
        mDataSize = isCyclic ? mDefaultVisibleNum : mDatas.size();        
        int halfIndex = isCyclic ? 7 : Math.min(mVisibleHalfNum, mDefaultHalfNum);
  //        LogUtil.i(TAG, "endIndex:" + endIndex + ",topPos:" + topPos + ",centerPos:" + centerPos + ",bottomPos:" + bottomPos + ",halfIndex:" + halfIndex);
  //        LogUtil.i(TAG, "endIndex:" + endIndex + ",mVisibleHalfNum:" + mVisibleHalfNum + ",mDefaultHalfNum:" + mDefaultHalfNum + ",mDefaultVisibleNum:" + mDefaultVisibleNum);
        int bottom = 0;
        mData.add(0, mDatas.get(centerPos));        
        if ((isCyclic && mDatas.size() < 13) || mDatas.size() >= 13) {      
              for (int i = 1; i < halfIndex; i++) {
                bottom = centerPos - i;
                bottom = chooseIndex(bottom, mDatas.size());
                mData.add(mDatas.get(bottom));
            }            
            
            for (int i = mDatas.size() - 1; i >= halfIndex; i--) {
                bottom = centerPos + mDatas.size() - i;
                bottom = chooseIndex(bottom, mDatas.size());
                mData.add(mDatas.get(bottom));
            }            
            
            if (isCyclic)         
               for (int i = mDatas.size(); i < 13; i++) {
                    bottom = i - halfIndex + 1 + centerPos;
                    bottom = chooseIndex(bottom, mDatas.size());
    //                    LogUtil.e(TAG, "bottom:" + bottom + ",:" + mDatas.get(bottom));
                    mData.add(mDatas.get(bottom));
                }

        } else if (mDatas.size() < 13) {        
            //下面个数比上面多
            for (int i = 1; i <= halfIndex; i++) {
                bottom = centerPos - i;
                bottom = chooseIndex(bottom, mDatas.size());
                mData.add(mDatas.get(bottom));
            }            
            
            for (int i = mDatas.size() - 1; i > halfIndex; i--) {
                bottom = centerPos + mDatas.size() - i;
                bottom = chooseIndex(bottom, mDatas.size());
                mData.add(mDatas.get(bottom));
            }
        }
        invalidate();
    }    
    
    public void setTextSize(float textSize) {   
        this.mItemTextSize = textSize;
        invalidate();
    }    
    
    public void setTextSelectColor(int colorRes) {    
        this.mSelectedItemTextColor = colorRes;
        requestLayout();
        invalidate();
    }    
    
    public int chooseIndex(int index, int dataSize) {   
        if (index > dataSize - 1)
            index -= dataSize;        
        if (index < 0)
            index += dataSize;       
        return index;
    }    
    
    public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {     
       this.mOnItemSelectedListener = onItemSelectedListener;
    }    
    
    public int getListSize() {     
       if (null != mDataCC)            
           return mDataCC.size();        
       return 0;
    }    
    
    /**
     * 滚轮选择器Item项被选中时监听接口
     */
    public interface OnItemSelectedListener {
        void onItemSelected(CircleWheelView view, Object data, int position);
    }
}

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
go语言 注释编码
go语言 注释编码

本专题整合了go语言注释、注释规范等等内容,阅读专题下面的文章了解更多详细内容。

2

2026.01.31

go语言 math包
go语言 math包

本专题整合了go语言math包相关内容,阅读专题下面的文章了解更多详细内容。

1

2026.01.31

go语言输入函数
go语言输入函数

本专题整合了go语言输入相关教程内容,阅读专题下面的文章了解更多详细内容。

1

2026.01.31

golang 循环遍历
golang 循环遍历

本专题整合了golang循环遍历相关教程,阅读专题下面的文章了解更多详细内容。

0

2026.01.31

Golang人工智能合集
Golang人工智能合集

本专题整合了Golang人工智能相关内容,阅读专题下面的文章了解更多详细内容。

1

2026.01.31

2026赚钱平台入口大全
2026赚钱平台入口大全

2026年最新赚钱平台入口汇总,涵盖任务众包、内容创作、电商运营、技能变现等多类正规渠道,助你轻松开启副业增收之路。阅读专题下面的文章了解更多详细内容。

76

2026.01.31

高干文在线阅读网站大全
高干文在线阅读网站大全

汇集热门1v1高干文免费阅读资源,涵盖都市言情、京味大院、军旅高干等经典题材,情节紧凑、人物鲜明。阅读专题下面的文章了解更多详细内容。

73

2026.01.31

无需付费的漫画app大全
无需付费的漫画app大全

想找真正免费又无套路的漫画App?本合集精选多款永久免费、资源丰富、无广告干扰的优质漫画应用,涵盖国漫、日漫、韩漫及经典老番,满足各类阅读需求。阅读专题下面的文章了解更多详细内容。

67

2026.01.31

漫画免费在线观看地址大全
漫画免费在线观看地址大全

想找免费又资源丰富的漫画网站?本合集精选2025-2026年热门平台,涵盖国漫、日漫、韩漫等多类型作品,支持高清流畅阅读与离线缓存。阅读专题下面的文章了解更多详细内容。

19

2026.01.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.8万人学习

PHP自制框架
PHP自制框架

共8课时 | 0.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号