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代码:

相关内容

热门资讯

常用商务英语口语   商务英语是以适应职场生活的语言要求为目的,内容涉及到商务活动的方方面面。下面是小编收集的常用商务...
六年级上册英语第一单元练习题   一、根据要求写单词。  1.dry(反义词)__________________  2.writ...
复活节英文怎么说 复活节英文怎么说?复活节的英语翻译是什么?复活节:Easter;"Easter,anniversar...
2008年北京奥运会主题曲 2008年北京奥运会(第29届夏季奥林匹克运动会),2008年8月8日到2008年8月24日在中华人...
英语道歉信 英语道歉信15篇  在日常生活中,道歉信的使用频率越来越高,通过道歉信,我们可以更好地解释事情发生的...
六年级英语专题训练(连词成句... 六年级英语专题训练(连词成句30题)  1. have,playhouse,many,I,toy,i...
上班迟到情况说明英语   每个人都或多或少的迟到过那么几次,因为各种原因,可能生病,可能因为交通堵车,可能是因为天气冷,有...
小学英语教学论文 小学英语教学论文范文  引导语:英语教育一直都是每个家长所器重的,那么有关小学英语教学论文要怎么写呢...
英语口语学习必看的方法技巧 英语口语学习必看的方法技巧如何才能说流利的英语? 说外语时,我们主要应做到四件事:理解、回答、提问、...
四级英语作文选:Birth ... 四级英语作文范文选:Birth controlSince the Chinese Governmen...
金融专业英语面试自我介绍 金融专业英语面试自我介绍3篇  金融专业的学生面试时,面试官要求用英语做自我介绍该怎么说。下面是小编...
我的李老师走了四年级英语日记... 我的李老师走了四年级英语日记带翻译  我上了五个学期的小学却换了六任老师,李老师是带我们班最长的语文...
小学三年级英语日记带翻译捡玉... 小学三年级英语日记带翻译捡玉米  今天,我和妈妈去外婆家,外婆家有刚剥的`玉米棒上带有玉米籽,好大的...
七年级英语优秀教学设计 七年级英语优秀教学设计  作为一位兢兢业业的人民教师,常常要写一份优秀的教学设计,教学设计是把教学原...
我的英语老师作文 我的英语老师作文(通用21篇)  在日常生活或是工作学习中,大家都有写作文的经历,对作文很是熟悉吧,...
英语老师教学经验总结 英语老师教学经验总结(通用19篇)  总结是指社会团体、企业单位和个人对某一阶段的学习、工作或其完成...
初一英语暑假作业答案 初一英语暑假作业答案  英语练习一(基础训练)第一题1.D2.H3.E4.F5.I6.A7.J8.C...
大学生的英语演讲稿 大学生的英语演讲稿范文(精选10篇)  使用正确的写作思路书写演讲稿会更加事半功倍。在现实社会中,越...
VOA美国之音英语学习网址 VOA美国之音英语学习推荐网址 美国之音网站已经成为语言学习最重要的资源站点,在互联网上还有若干网站...
商务英语期末试卷 Part I Term Translation (20%)Section A: Translate ...