Android 圆弧形 SeekBar
创始人
2024-05-28 14:04:42
0

效果预览

package com.gcssloop.widget;

import android.annotation.SuppressLint;

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Matrix;

import android.graphics.Paint;

import android.graphics.Path;

import android.graphics.PathMeasure;

import android.graphics.RectF;

import android.graphics.Region;

import android.graphics.SweepGradient;

import android.os.Bundle;

import android.os.Parcelable;

import android.util.AttributeSet;

import android.util.TypedValue;

import android.view.GestureDetector;

import android.view.MotionEvent;

import android.view.View;

import com.gcssloop.arcseekbar.R;

import static android.view.MotionEvent.ACTION_CANCEL;

import static android.view.MotionEvent.ACTION_DOWN;

import static android.view.MotionEvent.ACTION_MOVE;

import static android.view.MotionEvent.ACTION_UP;

public class ArcSeekBar extends View {

private static final int DEFAULT_EDGE_LENGTH = 260; // 默认宽高

private static final float CIRCLE_ANGLE = 360; // 圆周角

private static final int DEFAULT_ARC_WIDTH = 40; // 默认宽度 dp

private static final float DEFAULT_OPEN_ANGLE = 120; // 开口角度

private static final float DEFAULT_ROTATE_ANGLE = 90; // 旋转角度

private static final int DEFAULT_BORDER_WIDTH = 0; // 默认描边宽度

private static final int DEFAULT_BORDER_COLOR = 0xffffffff; // 默认描边颜色

private static final int DEFAULT_THUMB_COLOR = 0xffffffff; // 拖动按钮颜色

private static final int DEFAULT_THUMB_WIDTH = 2; // 拖动按钮描边宽度 dp

private static final int DEFAULT_THUMB_RADIUS = 15; // 拖动按钮半径 dp

private static final int DEFAULT_THUMB_SHADOW_RADIUS = 0; // 拖动按钮阴影半径 dp

private static final int DEFAULT_THUMB_SHADOW_COLOR = 0xFF000000; // 拖动按钮阴影颜色

private static final int DEFAULT_SHADOW_RADIUS = 0; // 默认阴影半径 dp

private static final int THUMB_MODE_STROKE = 0; // 拖动按钮模式 - 描边

private static final int THUMB_MODE_FILL = 1; // 拖动按钮模式 - 填充

private static final int THUMB_MODE_FILL_STROKE = 2; // 拖动按钮模式 - 填充+描边

private static final int DEFAULT_MAX_VALUE = 100; // 默认最大数值

private static final int DEFAULT_MIN_VALUE = 0; // 默认最小数值

private static final String KEY_PROGRESS_PRESENT = "PRESENT"; // 用于存储和获取当前百分比

// 可配置数据

private int[] mArcColors; // Seek 颜色

private float mArcWidth; // Seek 宽度

private float mOpenAngle; // 开口的角度大小 0 - 360

private float mRotateAngle; // 旋转角度

private int mBorderWidth; // 描边宽度

private int mBorderColor; // 描边颜色

private int mThumbColor; // 拖动按钮颜色

private float mThumbWidth; // 拖动按钮宽度

private float mThumbRadius; // 拖动按钮半径

private float mThumbShadowRadius;// 拖动按钮阴影半径

private int mThumbShadowColor;// 拖动按钮阴影颜色

private int mThumbMode; // 拖动按钮模式

private int mShadowRadius; // 阴影半径

private int mMaxValue; // 最大数值

private int mMinValue; // 最小数值

private float mCenterX; // 圆弧 SeekBar 中心点 X

private float mCenterY; // 圆弧 SeekBar 中心点 Y

private float mThumbX; // 拖动按钮 中心点 X

private float mThumbY; // 拖动按钮 中心点 Y

private Path mSeekPath;

private Path mBorderPath;

private Paint mArcPaint;

private Paint mThumbPaint;

private Paint mBorderPaint;

private Paint mShadowPaint;

private float[] mTempPos;

private float[] mTempTan;

private PathMeasure mSeekPathMeasure;

private float mProgressPresent = 0; // 当前进度百分比

private boolean mCanDrag = false; // 是否允许拖动

private boolean mAllowTouchSkip = false; // 是否允许越过边界

private GestureDetector mDetector;

private Matrix mInvertMatrix; // 逆向 Matrix, 用于计算触摸坐标和绘制坐标的转换

private Region mArcRegion; // ArcPath的实际区域大小,用于判定单击事件

public ArcSeekBar(Context context) {

this(context, null);

}

public ArcSeekBar(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public ArcSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

setSaveEnabled(true);

setLayerType(LAYER_TYPE_SOFTWARE, null);

initAttrs(context, attrs);

initData();

initPaint();

}

// 初始化各种属性

private void initAttrs(Context context, AttributeSet attrs) {

TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ArcSeekBar);

mArcColors = getArcColors(context, ta);

mArcWidth = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_width, dp2px(DEFAULT_ARC_WIDTH));

mOpenAngle = ta.getFloat(R.styleable.ArcSeekBar_arc_open_angle, DEFAULT_OPEN_ANGLE);

mRotateAngle = ta.getFloat(R.styleable.ArcSeekBar_arc_rotate_angle, DEFAULT_ROTATE_ANGLE);

mMaxValue = ta.getInt(R.styleable.ArcSeekBar_arc_max, DEFAULT_MAX_VALUE);

mMinValue = ta.getInt(R.styleable.ArcSeekBar_arc_min, DEFAULT_MIN_VALUE);

// 如果用户设置的最大值和最小值不合理,则直接按照默认进行处理

if (mMaxValue <= mMinValue) {

mMaxValue = DEFAULT_MAX_VALUE;

mMinValue = DEFAULT_MIN_VALUE;

}

int progress = ta.getInt(R.styleable.ArcSeekBar_arc_progress, mMinValue);

setProgress(progress);

mBorderWidth = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_border_width, dp2px(DEFAULT_BORDER_WIDTH));

mBorderColor = ta.getColor(R.styleable.ArcSeekBar_arc_border_color, DEFAULT_BORDER_COLOR);

mThumbColor = ta.getColor(R.styleable.ArcSeekBar_arc_thumb_color, DEFAULT_THUMB_COLOR);

mThumbRadius = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_thumb_radius, dp2px(DEFAULT_THUMB_RADIUS));

mThumbShadowRadius = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_thumb_shadow_radius, dp2px(DEFAULT_THUMB_SHADOW_RADIUS));

mThumbShadowColor = ta.getColor(R.styleable.ArcSeekBar_arc_thumb_shadow_color, DEFAULT_THUMB_SHADOW_COLOR);

mThumbWidth = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_thumb_width, dp2px(DEFAULT_THUMB_WIDTH));

mThumbMode = ta.getInt(R.styleable.ArcSeekBar_arc_thumb_mode, THUMB_MODE_STROKE);

mShadowRadius = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_shadow_radius, dp2px(DEFAULT_SHADOW_RADIUS));

ta.recycle();

}

// 获取 Arc 颜色数组

private int[] getArcColors(Context context, TypedArray ta) {

int[] ret;

int resId = ta.getResourceId(R.styleable.ArcSeekBar_arc_colors, 0);

if (0 == resId) {

resId = R.array.arc_colors_default;

}

ret = getColorsByArrayResId(context, resId);

return ret;

}

// 根据 resId 获取颜色数组

private int[] getColorsByArrayResId(Context context, int resId) {

int[] ret;

TypedArray colorArray = context.getResources().obtainTypedArray(resId);

ret = new int[colorArray.length()];

for (int i = 0; i < colorArray.length(); i++) {

ret[i] = colorArray.getColor(i, 0);

}

return ret;

}

// 初始化数据

private void initData() {

mSeekPath = new Path();

mBorderPath = new Path();

mSeekPathMeasure = new PathMeasure();

mTempPos = new float[2];

mTempTan = new float[2];

mDetector = new GestureDetector(getContext(), new OnClickListener());

mInvertMatrix = new Matrix();

mArcRegion = new Region();

}

// 初始化画笔

private void initPaint() {

initArcPaint();

initThumbPaint();

initBorderPaint();

initShadowPaint();

}

// 初始化圆弧画笔

private void initArcPaint() {

mArcPaint = new Paint();

mArcPaint.setAntiAlias(true);

mArcPaint.setStrokeWidth(mArcWidth);

mArcPaint.setStyle(Paint.Style.STROKE);

mArcPaint.setStrokeCap(Paint.Cap.ROUND);

}

// 初始化拖动按钮画笔

private void initThumbPaint() {

mThumbPaint = new Paint();

mThumbPaint.setAntiAlias(true);

mThumbPaint.setColor(mThumbColor);

mThumbPaint.setStrokeWidth(mThumbWidth);

mThumbPaint.setStrokeCap(Paint.Cap.ROUND);

if (mThumbMode == THUMB_MODE_FILL) {

mThumbPaint.setStyle(Paint.Style.FILL_AND_STROKE);

} else if (mThumbMode == THUMB_MODE_FILL_STROKE) {

mThumbPaint.setStyle(Paint.Style.FILL_AND_STROKE);

} else {

mThumbPaint.setStyle(Paint.Style.STROKE);

}

mThumbPaint.setTextSize(56);

}

// 初始化拖动按钮画笔

private void initBorderPaint() {

mBorderPaint = new Paint();

mBorderPaint.setAntiAlias(true);

mBorderPaint.setColor(mBorderColor);

mBorderPaint.setStrokeWidth(mBorderWidth);

mBorderPaint.setStyle(Paint.Style.STROKE);

}

// 初始化阴影画笔

private void initShadowPaint() {

mShadowPaint = new Paint();

mShadowPaint.setAntiAlias(true);

mShadowPaint.setStrokeWidth(mBorderWidth);

mShadowPaint.setStyle(Paint.Style.FILL_AND_STROKE);

}

@Override

protected Parcelable onSaveInstanceState() {

Bundle bundle = new Bundle();

bundle.putParcelable("superState", super.onSaveInstanceState());

bundle.putFloat(KEY_PROGRESS_PRESENT, mProgressPresent);

return bundle;

}

@Override

protected void onRestoreInstanceState(Parcelable state) {

if (state instanceof Bundle) {

Bundle bundle = (Bundle) state;

this.mProgressPresent = bundle.getFloat(KEY_PROGRESS_PRESENT);

state = bundle.getParcelable("superState");

}

if (null != mOnProgressChangeListener) {

mOnProgressChangeListener.onProgressChanged(this, getProgress(), false);

}

super.onRestoreInstanceState(state);

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int ws = MeasureSpec.getSize(widthMeasureSpec); //取出宽度的确切数值

int wm = MeasureSpec.getMode(widthMeasureSpec); //取出宽度的测量模式

int hs = MeasureSpec.getSize(heightMeasureSpec); //取出高度的确切数值

int hm = MeasureSpec.getMode(heightMeasureSpec); //取出高度的测量模

if (wm == MeasureSpec.UNSPECIFIED) {

wm = MeasureSpec.EXACTLY;

ws = dp2px(DEFAULT_EDGE_LENGTH);

} else if (wm == MeasureSpec.AT_MOST) {

wm = MeasureSpec.EXACTLY;

ws = Math.min(dp2px(DEFAULT_EDGE_LENGTH), ws);

}

if (hm == MeasureSpec.UNSPECIFIED) {

hm = MeasureSpec.EXACTLY;

hs = dp2px(DEFAULT_EDGE_LENGTH);

} else if (hm == MeasureSpec.AT_MOST) {

hm = MeasureSpec.EXACTLY;

hs = Math.min(dp2px(DEFAULT_EDGE_LENGTH), hs);

}

setMeasuredDimension(MeasureSpec.makeMeasureSpec(ws, wm), MeasureSpec.makeMeasureSpec(hs, hm));

}

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

// 计算在当前大小下,内容应该显示的大小和起始位置

int safeW = w - getPaddingLeft() - getPaddingRight();

int safeH = h - getPaddingTop() - getPaddingBottom();

float edgeLength, startX, startY;

float fix = mArcWidth / 2 + mBorderWidth + mShadowRadius * 2; // 修正距离,画笔宽度的修正

if (safeW < safeH) {

// 宽度小于高度,以宽度为准

edgeLength = safeW - fix;

startX = getPaddingLeft();

startY = (safeH - safeW) / 2.0f + getPaddingTop();

} else {

// 宽度大于高度,以高度为准

edgeLength = safeH - fix;

startX = (safeW - safeH) / 2.0f + getPaddingLeft();

startY = getPaddingTop();

}

// 得到显示区域和中心的

RectF content = new RectF(startX + fix, startY + fix, startX + edgeLength, startY + edgeLength);

mCenterX = content.centerX();

mCenterY = content.centerY();

// 得到路径

mSeekPath.reset();

mSeekPath.addArc(content, mOpenAngle / 2, CIRCLE_ANGLE - mOpenAngle);

mSeekPathMeasure.setPath(mSeekPath, false);

computeThumbPos(mProgressPresent);

resetShaderColor();

mInvertMatrix.reset();

mInvertMatrix.preRotate(-mRotateAngle, mCenterX, mCenterY);

mArcPaint.getFillPath(mSeekPath, mBorderPath);

mBorderPath.close();

mArcRegion.setPath(mBorderPath, new Region(0, 0, w, h));

}

// 重置 shader 颜色

private void resetShaderColor() {

// 计算渐变数组

float startPos = (mOpenAngle / 2) / CIRCLE_ANGLE;

float stopPos = (CIRCLE_ANGLE - (mOpenAngle / 2)) / CIRCLE_ANGLE;

int len = mArcColors.length - 1;

float distance = (stopPos - startPos) / len;

float pos[] = new float[mArcColors.length];

for (int i = 0; i < mArcColors.length; i++) {

pos[i] = startPos + (distance * i);

}

SweepGradient gradient = new SweepGradient(mCenterX, mCenterY, mArcColors, pos);

mArcPaint.setShader(gradient);

}

// 具体绘制

@Override

protected void onDraw(Canvas canvas) {

canvas.save();

canvas.rotate(mRotateAngle, mCenterX, mCenterY);

mShadowPaint.setShadowLayer(mShadowRadius * 2, 0, 0, getColor());

canvas.drawPath(mBorderPath, mShadowPaint);

canvas.drawPath(mSeekPath, mArcPaint);

if (mBorderWidth > 0) {

canvas.drawPath(mBorderPath, mBorderPaint);

}

if (mThumbShadowRadius > 0) {

mThumbPaint.setShadowLayer(mThumbShadowRadius, 0, 0, mThumbShadowColor);

canvas.drawCircle(mThumbX, mThumbY, mThumbRadius, mThumbPaint);

mThumbPaint.clearShadowLayer();

}

canvas.drawCircle(mThumbX, mThumbY, mThumbRadius, mThumbPaint);

canvas.restore();

}

private boolean moved = false;

private int lastProgress = -1;

@SuppressLint("ClickableViewAccessibility")

@Override

public boolean onTouchEvent(MotionEvent event) {

super.onTouchEvent(event);

int action = event.getActionMasked();

switch (action) {

case ACTION_DOWN:

moved = false;

judgeCanDrag(event);

if (null != mOnProgressChangeListener) {

mOnProgressChangeListener.onStartTrackingTouch(this);

}

break;

case ACTION_MOVE:

if (!mCanDrag) {

break;

}

float tempProgressPresent = getCurrentProgress(event.getX(), event.getY());

if (!mAllowTouchSkip) {

// 不允许突变

if (Math.abs(tempProgressPresent - mProgressPresent) > 0.5f) {

break;

}

}

// 允许突变 或者非突变

mProgressPresent = tempProgressPresent;

computeThumbPos(mProgressPresent);

// 事件回调

if (null != mOnProgressChangeListener && getProgress() != lastProgress) {

mOnProgressChangeListener.onProgressChanged(this, getProgress(), true);

lastProgress = getProgress();

}

moved = true;

break;

case ACTION_UP:

case ACTION_CANCEL:

if (null != mOnProgressChangeListener && moved) {

mOnProgressChangeListener.onStopTrackingTouch(this);

}

break;

}

mDetector.onTouchEvent(event);

invalidate();

return true;

}

// 判断是否允许拖动

private void judgeCanDrag(MotionEvent event) {

float[] pos = {event.getX(), event.getY()};

mInvertMatrix.mapPoints(pos);

if (getDistance(pos[0], pos[1]) <= mThumbRadius * 1.5) {

mCanDrag = true;

} else {

mCanDrag = false;

}

}

private class OnClickListener extends GestureDetector.SimpleOnGestureListener {

@Override

public boolean onSingleTapUp(MotionEvent e) {

// 判断是否点击在了进度区域

if (!isInArcProgress(e.getX(), e.getY())) return false;

// 点击允许突变

mProgressPresent = getCurrentProgress(e.getX(), e.getY());

computeThumbPos(mProgressPresent);

// 事件回调

if (null != mOnProgressChangeListener) {

mOnProgressChangeListener.onProgressChanged(ArcSeekBar.this, getProgress(), true);

mOnProgressChangeListener.onStopTrackingTouch(ArcSeekBar.this);

}

return true;

}

}

// 判断该点是否在进度条上面

private boolean isInArcProgress(float px, float py) {

float[] pos = {px, py};

mInvertMatrix.mapPoints(pos);

return mArcRegion.contains((int) pos[0], (int) pos[1]);

}

// 获取当前进度理论进度数值

private float getCurrentProgress(float px, float py) {

float diffAngle = getDiffAngle(px, py);

float progress = diffAngle / (CIRCLE_ANGLE - mOpenAngle);

if (progress < 0) progress = 0;

if (progress > 1) progress = 1;

return progress;

}

// 获得当前点击位置所成角度与开始角度之间的数值差

private float getDiffAngle(float px, float py) {

float angle = getAngle(px, py);

float diffAngle;

diffAngle = angle - mRotateAngle;

if (diffAngle < 0) {

diffAngle = (diffAngle + CIRCLE_ANGLE) % CIRCLE_ANGLE;

}

diffAngle = diffAngle - mOpenAngle / 2;

return diffAngle;

}

// 计算指定位置与内容区域中心点的夹角

private float getAngle(float px, float py) {

float angle = (float) ((Math.atan2(py - mCenterY, px - mCenterX)) * 180 / 3.14f);

if (angle < 0) {

angle += 360;

}

return angle;

}

// 计算指定位置与上次位置的距离

private float getDistance(float px, float py) {

return (float) Math.sqrt((px - mThumbX) * (px - mThumbX) + (py - mThumbY) * (py - mThumbY));

}

private int dp2px(int dp) {

return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics());

}

// 计算拖动块应该显示的位置

private void computeThumbPos(float present) {

if (present < 0) present = 0;

if (present > 1) present = 1;

if (null == mSeekPathMeasure) return;

float distance = mSeekPathMeasure.getLength() * present;

mSeekPathMeasure.getPosTan(distance, mTempPos, mTempTan);

mThumbX = mTempPos[0];

mThumbY = mTempPos[1];

}

/**

* 获取当前进度的具体颜色

*

* @return 当前进度在渐变中的颜色

*/

public int getColor() {

return getColor(mProgressPresent);

}

/**

* 获取某个百分比位置的颜色

*

* @param radio 取值[0,1]

* @return 最终颜色

*/

private int getColor(float radio) {

float diatance = 1.0f / (mArcColors.length - 1);

int startColor;

int endColor;

if (radio >= 1) {

return mArcColors[mArcColors.length - 1];

}

for (int i = 0; i < mArcColors.length; i++) {

if (radio <= i * diatance) {

if (i == 0) {

return mArcColors[0];

}

startColor = mArcColors[i - 1];

endColor = mArcColors[i];

float areaRadio = getAreaRadio(radio, diatance * (i - 1), diatance * i);

return getColorFrom(startColor, endColor, areaRadio);

}

}

return -1;

}

/**

* 计算当前比例在子区间的比例

*

* @param radio 总比例

* @param startPosition 子区间开始位置

* @param endPosition 子区间结束位置

* @return 自区间比例[0, 1]

*/

private float getAreaRadio(float radio, float startPosition, float endPosition) {

return (radio - startPosition) / (endPosition - startPosition);

}

/**

* 取两个颜色间的渐变区间 中的某一点的颜色

*

* @param startColor 开始的颜色

* @param endColor 结束的颜色

* @param radio 比例 [0, 1]

* @return 选中点的颜色

*/

private int getColorFrom(int startColor, int endColor, float radio) {

int redStart = Color.red(startColor);

int blueStart = Color.blue(startColor);

int greenStart = Color.green(startColor);

int redEnd = Color.red(endColor);

int blueEnd = Color.blue(endColor);

int greenEnd = Color.green(endColor);

int red = (int) (redStart + ((redEnd - redStart) * radio + 0.5));

int greed = (int) (greenStart + ((greenEnd - greenStart) * radio + 0.5));

int blue = (int) (blueStart + ((blueEnd - blueStart) * radio + 0.5));

return Color.argb(255, red, greed, blue);

}

/**

* 设置进度

*

* @param progress 进度值

*/

public void setProgress(int progress) {

System.out.println("setProgress = " + progress);

if (progress > mMaxValue) progress = mMaxValue;

if (progress < mMinValue) progress = mMinValue;

mProgressPresent = (progress - mMinValue) * 1.0f / (mMaxValue - mMinValue);

System.out.println("setProgress present = " + mProgressPresent);

if (null != mOnProgressChangeListener) {

mOnProgressChangeListener.onProgressChanged(this, progress, false);

}

computeThumbPos(mProgressPresent);

postInvalidate();

}

/**

* 获取当前进度数值

*

* @return 当前进度数值

*/

public int getProgress() {

return (int) (mProgressPresent * (mMaxValue - mMinValue)) + mMinValue;

}

/**

* 设置颜色

*

* @param colors 颜色

*/

public void setArcColors(int[] colors) {

mArcColors = colors;

resetShaderColor();

postInvalidate();

}

/**

* 设置最大数值

* @param max 最大数值

*/

public void setMaxValue(int max) {

mMaxValue = max;

}

/**

* 设置最小数值

* @param min 最小数值

*/

public void setMinValue(int min) {

mMinValue = min;

}

/**

* 设置颜色

*

* @param colorArrayRes 颜色资源 R.array.arc_color

*/

public void setArcColors(int colorArrayRes) {

setArcColors(getColorsByArrayResId(getContext(), colorArrayRes));

}

private OnProgressChangeListener mOnProgressChangeListener;

public void setOnProgressChangeListener(OnProgressChangeListener onProgressChangeListener) {

mOnProgressChangeListener = onProgressChangeListener;

}

public interface OnProgressChangeListener {

/**

* 进度发生变化

*

* @param seekBar 拖动条

* @param progress 当前进度数值

* @param isUser 是否是用户操作, true 表示用户拖动, false 表示通过代码设置

*/

void onProgressChanged(ArcSeekBar seekBar, int progress, boolean isUser);

/**

* 用户开始拖动

*

* @param seekBar 拖动条

*/

void onStartTrackingTouch(ArcSeekBar seekBar);

/**

* 用户结束拖动

*

* @param seekBar 拖动条

*/

void onStopTrackingTouch(ArcSeekBar seekBar);

}

}

attrs.xml代码:

相关内容

热门资讯

四年级扫家作文400字【经典... 四年级扫家作文400字 篇一春天到了,我为了迎接新年的到来,特意要扫扫家里的卫生。我拿着扫把和簸箕,...
月球旅行记小学四年级作文60... 月球旅行记小学四年级作文600字 篇一我终于要去月球旅行啦!这是我人生中的第一次太空之旅。我非常激动...
四年级我感动的事500字作文... 四年级我感动的事500字作文 篇一饭堂阿姨的爱心举动我记得有一次,我在学校的饭堂吃午饭。当时,我刚刚...
沙滩飞车四年级作文【推荐3篇... 沙滩飞车四年级作文 篇一:我喜欢沙滩飞车沙滩飞车是一款非常流行的游戏,在游戏中我们可以驾驶各种各样的...
我最喜欢的电影四年级作文(优... 我最喜欢的电影四年级作文 篇一我最喜欢的电影是《疯狂动物城》。这是一部由迪士尼制作的动画电影,讲述了...
保姆机器人作文四年级300字... 篇一:保姆机器人的好处保姆机器人是一种能够帮助家庭照顾孩子和家务的智能机器人。它的出现给人们的生活带...
老师的话350字作文(优选3... 篇一:老师的话作为学生,我们每天都会听到老师的讲话。他们是我们的引路人,是我们的启发者,更是我们的朋...
捉蚊趣事作文四年级【优质6篇... 捉蚊趣事作文四年级 篇一我家的蚊子大战夏天来了,蚊子也来了。我家的蚊子实在太多了,每天晚上都被它们咬...
四年级描写家长作文300字(... 四年级描写家长作文300字 篇一家长是孩子成长道路上最重要的陪伴者和引路人。家长的关心、爱护和教导,...
小学四年级同步上册作文(通用... 小学四年级同步上册作文 篇一我的暑假计划暑假就要来了,我已经制定了一个丰富多彩的暑假计划。首先,我打...
孩子我为什么要打你四年级作文... 孩子我为什么要打你四年级作文 篇一最近,我读了一篇关于家庭教育的文章,深受启发。这篇文章告诉我,作为...
四年级发生在我身边的一件事作... 四年级发生在我身边的一件事作文 篇一我和小猫的故事上周末,我在家玩耍时,突然听到门外传来一阵咿咿呀呀...
小学生四年级推普小作文【通用... 小学生四年级推普小作文 篇一我喜欢的动物我喜欢的动物是熊猫。熊猫是一种非常可爱的动物,它有着黑白相间...
无私奉献的秦老师【实用3篇】 无私奉献的秦老师 篇一秦老师是一位备受学生喜爱的教师,他以无私奉献的精神,默默地为学生们付出着自己的...
四年级作文小金鱼(最新3篇) 四年级作文小金鱼 篇一小金鱼是我家的新宠物,它在我的房间里游来游去,给我带来了无尽的乐趣。小金鱼是一...
最好的朋友四年级小学生优秀作... 最好的朋友四年级小学生优秀作文 篇一我最好的朋友是一个四年级的小学生,她叫小芳。我们从一年级开始就是...
我爱夏天-四年级作文【实用6... 我爱夏天-四年级作文 篇一夏天是我最喜欢的季节,因为它给我带来了无尽的欢乐和惊喜。每年的夏天,我都期...
四年级200字作文(精彩3篇... 四年级200字作文 篇一我的暑假计划暑假即将来临,我已经制定了自己的暑假计划。首先,我打算利用这个假...
四年级拟人句【精选3篇】 四年级拟人句 篇一小草的自述我是一株小草,生长在操场的角落里。虽然我只有绿色的叶子和细细的根,但我有...
百家筝鸣四年级作文(推荐3篇... 百家筝鸣四年级作文 篇一百家筝鸣四年级作文我喜欢听音乐,尤其是古典音乐。最近,我参加了学校的音乐课程...