2017-10-11 15:16:04 qq_36255612 阅读数 1209
  • OpenGL实现Google地图瓦片的绘制漫游视频教程

    OpenGL实现Google地图瓦片的绘制漫游视频培训课程:此次教程所涉及的内容有OpenGL绘制图片、FreeImage加载图片、墨卡托投影、瓦片的金字塔模型、FramebufferObject、地图的移动和缩放优化,采用屏幕瓦片绘制优化、采用ImageBuffer优化、采用多线程优化、地图操作优化、模拟,生成全球瓦片(debug)、MFC-对话框中绘制地图、MFC-View中绘制地图、QT中绘制地图、在线浏览Google地图等。

    8876 人正在学习 去看看 张立铜

非常酷炫的自定义画圆,支持常规设置需求,满足大部分需求,上代码吧……

setp1:创建自定义控件

 

package xxx;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import xxx;

public class RoundProgressBar extends View {
        /**
         * 画笔对象的引用
         */
        private Paint paint;

        /**
         * 圆环的颜色
         */
        private int roundColor;

        /**
         * 圆环进度的颜色
         */
        private int roundProgressColor;

        /**
         * 中间进度百分比的字符串的颜色
         */
        private int textColor;

        /**
         * 中间进度百分比的字符串的字体
         */
        private float textSize;

        /**
         * 圆环的宽度
         */
        private float roundWidth;

        /**
         * 最大进度
         */
        private int max;

        /**
         * 当前进度
         */
        private float progress;
        /**
         * 是否显示中间的进度
         */
        private boolean textIsDisplayable;

        /**
         * 进度的风格,实心或者空心
         */
        private int style;

        public static final int STROKE = 0;
        public static final int FILL = 1;

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

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

        public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);

            paint = new Paint();


            TypedArray mTypedArray = context.obtainStyledAttributes(attrs,
                    R.styleable.RoundProgressBar);

            //获取自定义属性和默认值
            roundColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundColor, Color.RED);
            roundProgressColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundProgressColor, Color.GREEN);
            textColor = mTypedArray.getColor(R.styleable.RoundProgressBar_textColor, Color.GREEN);
            textSize = mTypedArray.getDimension(R.styleable.RoundProgressBar_textSize, 15);
            roundWidth = mTypedArray.getDimension(R.styleable.RoundProgressBar_roundWidth, 5);
            max = mTypedArray.getInteger(R.styleable.RoundProgressBar_max, 100);
            textIsDisplayable = mTypedArray.getBoolean(R.styleable.RoundProgressBar_textIsDisplayable, true);
            style = mTypedArray.getInt(R.styleable.RoundProgressBar_style, 0);

            mTypedArray.recycle();
        }


        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);

            /**
             * 画最外层的大圆环
             */
            int centre = getWidth()/2; //获取圆心的x坐标
            int radius = (int) (centre - roundWidth/2)-10; //圆环的半径
            paint.setColor(roundColor); //设置圆环的颜色
            paint.setStyle(Paint.Style.STROKE); //设置空心
            paint.setStrokeWidth(roundWidth-2); //设置圆环的宽度
            paint.setAntiAlias(true);  //消除锯齿
            canvas.drawCircle(centre, centre, radius, paint); //画出圆环

            Log.e("log", centre + "");

            /**
             * 画进度百分比
             */
            paint.setStrokeWidth(0);
            paint.setColor(textColor);
            paint.setTextSize(textSize);
            paint.setTypeface(Typeface.DEFAULT_BOLD); //设置字体
            int percent = (int)(((float)progress / (float)max) * 100);  //中间的进度百分比,先转换成float在进行除法运算,不然都为0
            float textWidth = paint.measureText(percent + "%");   //测量字体宽度,我们需要根据字体的宽度设置在圆环中间

            if(textIsDisplayable  && style == STROKE){
                canvas.drawText(percent + "%", centre - textWidth / 2, centre + textSize/2, paint); //画出进度百分比
            }


            /**
             * 画圆弧 ,画圆环的进度
             */

            //设置进度是实心还是空心
            paint.setStrokeWidth(roundWidth); //设置圆环的宽度
            paint.setColor(roundProgressColor);  //设置进度的颜色
            RectF oval = new RectF(centre - radius-1, centre - radius-1, centre
                    + radius+1, centre + radius+1);  //用于定义的圆弧的形状和大小的界限

            switch (style) {
                case STROKE:{
                    paint.setStyle(Paint.Style.STROKE);
                    canvas.drawArc(oval, 90, 360 * progress / max, false, paint);  //根据进度画圆弧
                    break;
                }
                case FILL:{
                    paint.setStyle(Paint.Style.FILL_AND_STROKE);
                    if(progress !=0)
                        canvas.drawArc(oval, 90, 360 * progress / max, true, paint);  //根据进度画圆弧
                    break;
                }
            }

        }


        public synchronized int getMax() {
            return max;
        }

        /**
         * 设置进度的最大值
         * @param max
         */
        public synchronized void setMax(int max) {
            if(max < 0){
                throw new IllegalArgumentException("max not less than 0");
            }
            this.max = max;
        }

        /**
         * 获取进度.需要同步
         * @return
         */
        public synchronized float getProgress() {
            return progress;
        }

        /**
         * 设置进度,此为线程安全控件,由于考虑多线的问题,需要同步
         * 刷新界面调用postInvalidate()能在非UI线程刷新
         * @param progress
         */
        public synchronized void setProgress(float progress) {
            if(progress < 0){
                throw new IllegalArgumentException("progress not less than 0");
            }
            if(progress > max){
                progress = max;
            }
            if(progress <= max){
                this.progress = progress;
                postInvalidate();
            }

        }


        public int getCricleColor() {
            return roundColor;
        }

        public void setCricleColor(int cricleColor) {
            this.roundColor = cricleColor;
        }

        public int getCricleProgressColor() {
            return roundProgressColor;
        }

        public void setCricleProgressColor(int cricleProgressColor) {
            this.roundProgressColor = cricleProgressColor;
        }

        public int getTextColor() {
            return textColor;
        }

        public void setTextColor(int textColor) {
            this.textColor = textColor;
        }

        public float getTextSize() {
            return textSize;
        }

        public void setTextSize(float textSize) {
            this.textSize = textSize;
        }

        public float getRoundWidth() {
            return roundWidth;
        }

        public void setRoundWidth(float roundWidth) {
            this.roundWidth = roundWidth;
        }
}

 

 

 

setp 2:

在values的attrs.xml文件中添加

 

<declare-styleable name="RoundProgressBar">
    <attr name="roundColor" format="color"/>
    <attr name="roundProgressColor" format="color"/>
    <attr name="roundWidth" format="dimension"></attr>
    <attr name="textColor" format="color" />
    <attr name="textSize" format="dimension" />
    <attr name="max" format="integer"></attr>
    <attr name="textIsDisplayable" format="boolean"></attr>
    <attr name="style">
        <enum name="STROKE" value="0"></enum>
        <enum name="FILL" value="1"></enum>
    </attr>
</declare-styleable>

 

 

setp3:

布局中使用

 

<xxx.RoundProgressBar
    android:id="@+id/roundProgressBar"
    android:layout_width="60dp"
    android:layout_height="match_parent"
    android:layout_centerInParent="true"
    android:layout_centerVertical="true"
    android_custom:max="100"
    android_custom:roundColor="#D1D1D1"
    android_custom:roundProgressColor="@color/yellow_color"
    android_custom:roundWidth="3dp"
    android_custom:textColor="@color/yellow_color"
    android_custom:textSize="15sp"
    />

 

 

 

到这里就搞定了……

 

 

2017-11-13 18:59:46 wk_beicai 阅读数 243
  • OpenGL实现Google地图瓦片的绘制漫游视频教程

    OpenGL实现Google地图瓦片的绘制漫游视频培训课程:此次教程所涉及的内容有OpenGL绘制图片、FreeImage加载图片、墨卡托投影、瓦片的金字塔模型、FramebufferObject、地图的移动和缩放优化,采用屏幕瓦片绘制优化、采用ImageBuffer优化、采用多线程优化、地图操作优化、模拟,生成全球瓦片(debug)、MFC-对话框中绘制地图、MFC-View中绘制地图、QT中绘制地图、在线浏览Google地图等。

    8876 人正在学习 去看看 张立铜
如何自定义一个时间控件:demo项目地址在文章最下方:

运行案例:

实现步骤:
1.自定义时间的控件的绘制(如何绘制自己喜欢的时间样式)
public class DatePickerView extends View {

private Context context;
private boolean loop = true;
public static final float MARGIN_ALPHA = 2.8f;
private List<String> mDataList;
private int mCurrentSelected;
private Paint mPaint, nPaint;
private float mMaxTextSize = 80;
private float mMinTextSize = 40;
private float mMaxTextAlpha = 255;
private float mMinTextAlpha = 120;
private int mViewHeight;
private int mViewWidth;
private float mLastDownY;
private float mMoveLen = 0;
private boolean isInit = false;
private boolean canScroll = true;
private onSelectListener mSelectListener;
private Timer timer;
private MyTimerTask mTask;

private Handler updateHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (Math.abs(mMoveLen) < SPEED) {
mMoveLen = 0;
if (mTask != null) {
mTask.cancel();
mTask = null;
performSelect();
}
} else {
mMoveLen = mMoveLen - mMoveLen / Math.abs(mMoveLen) * SPEED;
}
invalidate();
}
};
public DatePickerView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
init();
}
public void setOnSelectListener(onSelectListener listener) {
mSelectListener = listener;
}
private void performSelect() {
if (mSelectListener != null) {
mSelectListener.onSelect(mDataList.get(mCurrentSelected));
}
}
public void setData(List<String> datas) {
mDataList = datas;
mCurrentSelected = datas.size() / 4;
invalidate();
}
public void setSelected(int selected) {
mCurrentSelected = selected;
if (loop) {
int distance = mDataList.size() / 2 - mCurrentSelected;
if (distance < 0) {
for (int i = 0; i < -distance; i++) {
moveHeadToTail();
mCurrentSelected--;
}
} else if (distance > 0) {
for (int i = 0; i < distance; i++) {
moveTailToHead();
mCurrentSelected++;
}
}
}
invalidate();
}
public void setSelected(String mSelectItem) {
for (int i = 0; i < mDataList.size(); i++) {
if (mDataList.get(i).equals(mSelectItem)) {
setSelected(i);
break;
}
}
}
private void moveHeadToTail() {
if (loop) {
String head = mDataList.get(0);
mDataList.remove(0);
mDataList.add(head);
}
}
private void moveTailToHead() {
if (loop) {
String tail = mDataList.get(mDataList.size() - 1);
mDataList.remove(mDataList.size() - 1);
mDataList.add(0, tail);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mViewHeight = getMeasuredHeight();
mViewWidth = getMeasuredWidth();
// 按照View的高度计算字体大小
mMaxTextSize = mViewHeight / 7f;
mMinTextSize = mMaxTextSize / 2.2f;
isInit = true;
invalidate();
}
private void init() {
timer = new Timer();
mDataList = new ArrayList<>();
//第一个paint
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Style.FILL);
mPaint.setTextAlign(Align.CENTER);
mPaint.setColor(ContextCompat.getColor(context, R.color.text1));
//第二个paint
nPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
nPaint.setStyle(Style.FILL);
nPaint.setTextAlign(Align.CENTER);
nPaint.setColor(ContextCompat.getColor(context, R.color.text2));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isInit) {
drawData(canvas);
}
}
private void drawData(Canvas canvas) {
float scale = parabola(mViewHeight / 4.0f, mMoveLen);
float size = (mMaxTextSize - mMinTextSize) * scale + mMinTextSize;
mPaint.setTextSize(size);
mPaint.setAlpha((int) ((mMaxTextAlpha - mMinTextAlpha) * scale + mMinTextAlpha))
float x = (float) (mViewWidth / 2.0);
float y = (float) (mViewHeight / 2.0 + mMoveLen);
FontMetricsInt fmi = mPaint.getFontMetricsInt();
float baseline = (float) (y - (fmi.bottom / 2.0 + fmi.top / 2.0));
canvas.drawText(mDataList.get(mCurrentSelected), x, baseline, mPaint);
// 绘制上方data
for (int i = 1; (mCurrentSelected - i) >= 0; i++) {
drawOtherText(canvas, i, -1);
}
// 绘制下方data
for (int i = 1; (mCurrentSelected + i) < mDataList.size(); i++) {
drawOtherText(canvas, i, 1);
}
}

private void drawOtherText(Canvas canvas, int position, int type) {
float d = MARGIN_ALPHA * mMinTextSize * position + type * mMoveLen;
float scale = parabola(mViewHeight / 4.0f, d);
float size = (mMaxTextSize - mMinTextSize) * scale + mMinTextSize;
nPaint.setTextSize(size);
nPaint.setAlpha((int) ((mMaxTextAlpha - mMinTextAlpha) * scale + mMinTextAlpha));
float y = (float) (mViewHeight / 2.0 + type * d);
FontMetricsInt fmi = nPaint.getFontMetricsInt();
float baseline = (float) (y - (fmi.bottom / 2.0 + fmi.top / 2.0));
canvas.drawText(mDataList.get(mCurrentSelected + type * position),
(float) (mViewWidth / 2.0), baseline, nPaint);
}
private float parabola(float zero, float x) {
float f = (float) (1 - Math.pow(x / zero, 2));
return f < 0 ? 0 : f;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
doDown(event);
break;

case MotionEvent.ACTION_MOVE:
mMoveLen += (event.getY() - mLastDownY);
if (mMoveLen > MARGIN_ALPHA * mMinTextSize / 2) {
if (!loop && mCurrentSelected == 0) {
mLastDownY = event.getY();
invalidate();
return true;
}
if (!loop) {
mCurrentSelected--;
}
// 往下滑超过离开距离
moveTailToHead();
mMoveLen = mMoveLen - MARGIN_ALPHA * mMinTextSize;
} else if (mMoveLen < -MARGIN_ALPHA * mMinTextSize / 2) {
if (mCurrentSelected == mDataList.size() - 1) {
mLastDownY = event.getY();
invalidate();
return true;
}
if (!loop) {
mCurrentSelected++;
}
// 往上滑超过离开距离
moveHeadToTail();
mMoveLen = mMoveLen + MARGIN_ALPHA * mMinTextSize;
}
mLastDownY = event.getY();
invalidate();
break;
case MotionEvent.ACTION_UP:
doUp();
break;
}
return true;
}
private void doDown(MotionEvent event) {
if (mTask != null) {
mTask.cancel();
mTask = null;
}
mLastDownY = event.getY();
}
private void doUp() {
// 抬起手后mCurrentSelected的位置由当前位置move到中间选中位置
if (Math.abs(mMoveLen) < 0.0001) {
mMoveLen = 0;
return;
}
if (mTask != null) {
mTask.cancel();
mTask = null;
}
mTask = new MyTimerTask(updateHandler);
timer.schedule(mTask, 0, 10);
}
class MyTimerTask extends TimerTask {
Handler handler;
public MyTimerTask(Handler handler) {
this.handler = handler;
}
@Override
public void run() {
handler.sendMessage(handler.obtainMessage());
}
}
public interface onSelectListener {
void onSelect(String text);
}
public void setCanScroll(boolean canScroll) {
this.canScroll = canScroll;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return canScroll && super.dispatchTouchEvent(event);
}
public void setIsLoop(boolean isLoop) {
loop = isLoop;
}
}

2。自定义时间控件(功能的实现)
/*构造方法*/
public CustomDatePicker(Context context, ResultHandler resultHandler, String startDate, String endDate) {
if (isValidDate(startDate, "yyyy-MM-dd HH:mm") && isValidDate(endDate, "yyyy-MM-dd HH:mm")) {
canAccess = true;
this.context = context;
this.handler = resultHandler;
selectedCalender = Calendar.getInstance();
startCalendar = Calendar.getInstance();
endCalendar = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.CHINA);
try {
startCalendar.setTime(sdf.parse(startDate));
endCalendar.setTime(sdf.parse(endDate));
} catch (ParseException e) {
e.printStackTrace();
}
initDialog();
initView();
}
}
/*弹出dialog 进行时间的选择*/
private void initDialog() {
if (datePickerDialog == null) {
datePickerDialog = new Dialog(context, R.style.time_dialog);
datePickerDialog.setCancelable(false);/*dialog弹出后会点击屏幕或物理放回 dialog不消失*/
datePickerDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);/*应用程序窗体显示状态操作 无标题*/
datePickerDialog.setContentView(R.layout.custom_date_picker);/*设置弹出的布局*/
Window window = datePickerDialog.getWindow();
window.setGravity(Gravity.BOTTOM);/*在屏幕的下方弹出*/

WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(dm);
/*更改窗口大小 是先显示出来dialog show出来 才能设置宽高属性*/
WindowManager.LayoutParams lp = window.getAttributes();
lp.width = dm.widthPixels;
window.setAttributes(lp);
}
}

private void initView() {
year_pv = (DatePickerView) datePickerDialog.findViewById(R.id.year_pv);
month_pv = (DatePickerView) datePickerDialog.findViewById(R.id.month_pv);
day_pv = (DatePickerView) datePickerDialog.findViewById(R.id.day_pv);
hour_pv = (DatePickerView) datePickerDialog.findViewById(R.id.hour_pv);
minute_pv = (DatePickerView) datePickerDialog.findViewById(R.id.minute_pv);
tv_cancle = (TextView) datePickerDialog.findViewById(R.id.tv_cancle);
tv_select = (TextView) datePickerDialog.findViewById(R.id.tv_select);
hour_text = (TextView) datePickerDialog.findViewById(R.id.hour_text);
minute_text = (TextView) datePickerDialog.findViewById(R.id.minute_text);
year_text= (TextView) datePickerDialog.findViewById(R.id.year_text);
month_text= (TextView) datePickerDialog.findViewById(R.id.month_text);
day_text= (TextView) datePickerDialog.findViewById(R.id.day_text);
/*设置确定和取消按钮的点击事件*/
tv_cancle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
datePickerDialog.dismiss();
}
});

tv_select.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.CHINA);
handler.handle(sdf.format(selectedCalender.getTime()));
datePickerDialog.dismiss();
}
});
}

private void initParameter() {
startYear = startCalendar.get(Calendar.YEAR);
startMonth = startCalendar.get(Calendar.MONTH) + 1;
startDay = startCalendar.get(Calendar.DAY_OF_MONTH);
startHour = startCalendar.get(Calendar.HOUR_OF_DAY);
startMinute = startCalendar.get(Calendar.MINUTE);
endYear = endCalendar.get(Calendar.YEAR);
endMonth = endCalendar.get(Calendar.MONTH) + 1;
endDay = endCalendar.get(Calendar.DAY_OF_MONTH);
endHour = endCalendar.get(Calendar.HOUR_OF_DAY);
endMinute = endCalendar.get(Calendar.MINUTE);
spanYear = startYear != endYear;
spanMon = (!spanYear) && (startMonth != endMonth);
spanDay = (!spanMon) && (startDay != endDay);
spanHour = (!spanDay) && (startHour != endHour);
spanMin = (!spanHour) && (startMinute != endMinute);
selectedCalender.setTime(startCalendar.getTime());
}

private void initTimer() {
initArrayList();
if (spanYear) {
for (int i = startYear; i <= endYear; i++) {
year.add(String.valueOf(i));
}
for (int i = startMonth; i <= MAX_MONTH; i++) {
month.add(formatTimeUnit(i));
}
for (int i = startDay; i <= startCalendar.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
day.add(formatTimeUnit(i));
}

if ((scrollUnits & SCROLL_TYPE.HOUR.value) != SCROLL_TYPE.HOUR.value) {
hour.add(formatTimeUnit(startHour));
} else {
for (int i = startHour; i <= MAX_HOUR; i++) {
hour.add(formatTimeUnit(i));
}
}

if ((scrollUnits & SCROLL_TYPE.MINUTE.value) != SCROLL_TYPE.MINUTE.value) {
minute.add(formatTimeUnit(startMinute));
} else {
for (int i = startMinute; i <= MAX_MINUTE; i++) {
minute.add(formatTimeUnit(i));
}
}
} else if (spanMon) {
year.add(String.valueOf(startYear));
for (int i = startMonth; i <= endMonth; i++) {
month.add(formatTimeUnit(i));
}
for (int i = startDay; i <= startCalendar.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
day.add(formatTimeUnit(i));
}

if ((scrollUnits & SCROLL_TYPE.HOUR.value) != SCROLL_TYPE.HOUR.value) {
hour.add(formatTimeUnit(startHour));
} else {
for (int i = startHour; i <= MAX_HOUR; i++) {
hour.add(formatTimeUnit(i));
}
}

if ((scrollUnits & SCROLL_TYPE.MINUTE.value) != SCROLL_TYPE.MINUTE.value) {
minute.add(formatTimeUnit(startMinute));
} else {
for (int i = startMinute; i <= MAX_MINUTE; i++) {
minute.add(formatTimeUnit(i));
}
}
} else if (spanDay) {
year.add(String.valueOf(startYear));
month.add(formatTimeUnit(startMonth));
for (int i = startDay; i <= endDay; i++) {
day.add(formatTimeUnit(i));
}

if ((scrollUnits & SCROLL_TYPE.HOUR.value) != SCROLL_TYPE.HOUR.value) {
hour.add(formatTimeUnit(startHour));
} else {
for (int i = startHour; i <= MAX_HOUR; i++) {
hour.add(formatTimeUnit(i));
}
}

if ((scrollUnits & SCROLL_TYPE.MINUTE.value) != SCROLL_TYPE.MINUTE.value) {
minute.add(formatTimeUnit(startMinute));
} else {
for (int i = startMinute; i <= MAX_MINUTE; i++) {
minute.add(formatTimeUnit(i));
}
}
} else if (spanHour) {
year.add(String.valueOf(startYear));
month.add(formatTimeUnit(startMonth));
day.add(formatTimeUnit(startDay));

if ((scrollUnits & SCROLL_TYPE.HOUR.value) != SCROLL_TYPE.HOUR.value) {
hour.add(formatTimeUnit(startHour));
} else {
for (int i = startHour; i <= endHour; i++) {
hour.add(formatTimeUnit(i));
}
}

if ((scrollUnits & SCROLL_TYPE.MINUTE.value) != SCROLL_TYPE.MINUTE.value) {
minute.add(formatTimeUnit(startMinute));
} else {
for (int i = startMinute; i <= MAX_MINUTE; i++) {
minute.add(formatTimeUnit(i));
}
}
} else if (spanMin) {
year.add(String.valueOf(startYear));
month.add(formatTimeUnit(startMonth));
day.add(formatTimeUnit(startDay));
hour.add(formatTimeUnit(startHour));

if ((scrollUnits & SCROLL_TYPE.MINUTE.value) != SCROLL_TYPE.MINUTE.value) {
minute.add(formatTimeUnit(startMinute));
} else {
for (int i = startMinute; i <= endMinute; i++) {
minute.add(formatTimeUnit(i));
}
}
}
loadComponent();
}

/**
* 将“0-9”转换为“00-09”
*/
private String formatTimeUnit(int unit) {
return unit < 10 ? "0" + String.valueOf(unit) : String.valueOf(unit);
}

private void initArrayList() {
if (year == null) year = new ArrayList<>();
if (month == null) month = new ArrayList<>();
if (day == null) day = new ArrayList<>();
if (hour == null) hour = new ArrayList<>();
if (minute == null) minute = new ArrayList<>();
year.clear();
month.clear();
day.clear();
hour.clear();
minute.clear();
}

private void loadComponent() {
year_pv.setData(year);
month_pv.setData(month);
day_pv.setData(day);
hour_pv.setData(hour);
minute_pv.setData(minute);
year_pv.setSelected(0);
month_pv.setSelected(0);
day_pv.setSelected(0);
hour_pv.setSelected(0);
minute_pv.setSelected(0);
executeScroll();
}

private void addListener() {
year_pv.setOnSelectListener(new DatePickerView.onSelectListener() {
@Override
public void onSelect(String text) {
selectedCalender.set(Calendar.YEAR, Integer.parseInt(text));
monthChange();
}
});

month_pv.setOnSelectListener(new DatePickerView.onSelectListener() {
@Override
public void onSelect(String text) {
selectedCalender.set(Calendar.DAY_OF_MONTH, 1);
selectedCalender.set(Calendar.MONTH, Integer.parseInt(text) - 1);
dayChange();
}
});

day_pv.setOnSelectListener(new DatePickerView.onSelectListener() {
@Override
public void onSelect(String text) {
selectedCalender.set(Calendar.DAY_OF_MONTH, Integer.parseInt(text));
hourChange();
}
});

hour_pv.setOnSelectListener(new DatePickerView.onSelectListener() {
@Override
public void onSelect(String text) {
selectedCalender.set(Calendar.HOUR_OF_DAY, Integer.parseInt(text));
minuteChange();
}
});

minute_pv.setOnSelectListener(new DatePickerView.onSelectListener() {
@Override
public void onSelect(String text) {
selectedCalender.set(Calendar.MINUTE, Integer.parseInt(text));
}
});
}

private void monthChange() {
month.clear();
int selectedYear = selectedCalender.get(Calendar.YEAR);
if (selectedYear == startYear) {
for (int i = startMonth; i <= MAX_MONTH; i++) {
month.add(formatTimeUnit(i));
}
} else if (selectedYear == endYear) {
for (int i = 1; i <= endMonth; i++) {
month.add(formatTimeUnit(i));
}
} else {
for (int i = 1; i <= MAX_MONTH; i++) {
month.add(formatTimeUnit(i));
}
}
selectedCalender.set(Calendar.MONTH, Integer.parseInt(month.get(0)) - 1);
month_pv.setData(month);
month_pv.setSelected(0);
executeAnimator(month_pv);

month_pv.postDelayed(new Runnable() {
@Override
public void run() {
dayChange();
}
}, 100);
}

private void dayChange() {
day.clear();
int selectedYear = selectedCalender.get(Calendar.YEAR);
int selectedMonth = selectedCalender.get(Calendar.MONTH) + 1;
if (selectedYear == startYear && selectedMonth == startMonth) {
for (int i = startDay; i <= selectedCalender.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
day.add(formatTimeUnit(i));
}
} else if (selectedYear == endYear && selectedMonth == endMonth) {
for (int i = 1; i <= endDay; i++) {
day.add(formatTimeUnit(i));
}
} else {
for (int i = 1; i <= selectedCalender.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
day.add(formatTimeUnit(i));
}
}
selectedCalender.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day.get(0)));
day_pv.setData(day);
day_pv.setSelected(0);
executeAnimator(day_pv);
day_pv.postDelayed(new Runnable() {
@Override
public void run() {
hourChange();
}
}, 100);
}

private void hourChange() {
if ((scrollUnits & SCROLL_TYPE.HOUR.value) == SCROLL_TYPE.HOUR.value) {
hour.clear();
int selectedYear = selectedCalender.get(Calendar.YEAR);
int selectedMonth = selectedCalender.get(Calendar.MONTH) + 1;
int selectedDay = selectedCalender.get(Calendar.DAY_OF_MONTH);
if (selectedYear == startYear && selectedMonth == startMonth && selectedDay == startDay) {
for (int i = startHour; i <= MAX_HOUR; i++) {
hour.add(formatTimeUnit(i));
}
} else if (selectedYear == endYear && selectedMonth == endMonth && selectedDay == endDay) {
for (int i = MIN_HOUR; i <= endHour; i++) {
hour.add(formatTimeUnit(i));
}
} else {
for (int i = MIN_HOUR; i <= MAX_HOUR; i++) {
hour.add(formatTimeUnit(i));
}
}
selectedCalender.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour.get(0)));
hour_pv.setData(hour);
hour_pv.setSelected(0);
executeAnimator(hour_pv);
}

hour_pv.postDelayed(new Runnable() {
@Override
public void run() {
minuteChange();
}
}, 100);
}

private void minuteChange() {
if ((scrollUnits & SCROLL_TYPE.MINUTE.value) == SCROLL_TYPE.MINUTE.value) {
minute.clear();
int selectedYear = selectedCalender.get(Calendar.YEAR);
int selectedMonth = selectedCalender.get(Calendar.MONTH) + 1;
int selectedDay = selectedCalender.get(Calendar.DAY_OF_MONTH);
int selectedHour = selectedCalender.get(Calendar.HOUR_OF_DAY);
if (selectedYear == startYear && selectedMonth == startMonth && selectedDay == startDay && selectedHour == startHour) {
for (int i = startMinute; i <= MAX_MINUTE; i++) {
minute.add(formatTimeUnit(i));
}
} else if (selectedYear == endYear && selectedMonth == endMonth && selectedDay == endDay && selectedHour == endHour) {
for (int i = MIN_MINUTE; i <= endMinute; i++) {
minute.add(formatTimeUnit(i));
}
} else {
for (int i = MIN_MINUTE; i <= MAX_MINUTE; i++) {
minute.add(formatTimeUnit(i));
}
}
selectedCalender.set(Calendar.MINUTE, Integer.parseInt(minute.get(0)));
minute_pv.setData(minute);
minute_pv.setSelected(0);
executeAnimator(minute_pv);
}
executeScroll();
}
/*设置动画 当滚动的时候*/
private void executeAnimator(View view) {
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha", 1f, 0f, 1f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", 1f, 1.3f, 1f);
PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("scaleY", 1f, 1.3f, 1f);
ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY, pvhZ).setDuration(200).start();
}

private void executeScroll() {
year_pv.setCanScroll(year.size() > 1);
month_pv.setCanScroll(month.size() > 1);
day_pv.setCanScroll(day.size() > 1);
hour_pv.setCanScroll(hour.size() > 1 && (scrollUnits & SCROLL_TYPE.HOUR.value) == SCROLL_TYPE.HOUR.value);
minute_pv.setCanScroll(minute.size() > 1 && (scrollUnits & SCROLL_TYPE.MINUTE.value) == SCROLL_TYPE.MINUTE.value);
}

private int disScrollUnit(SCROLL_TYPE... scroll_types) {
if (scroll_types == null || scroll_types.length == 0) {
scrollUnits = SCROLL_TYPE.HOUR.value + SCROLL_TYPE.MINUTE.value;
} else {
for (SCROLL_TYPE scroll_type : scroll_types) {
scrollUnits ^= scroll_type.value;
}
}
return scrollUnits;
}
public void show(String time) {
if (canAccess) {
if (isValidDate(time, "yyyy-MM-dd")) {
if (startCalendar.getTime().getTime() < endCalendar.getTime().getTime()) {
canAccess = true;
initParameter();
initTimer();
addListener();
setSelectedTime(time);
datePickerDialog.show();
}
}else if(isValidDate(time, "yyyy-MM-dd HH:mm")){
if (startCalendar.getTime().getTime() < endCalendar.getTime().getTime()) {
canAccess = true;
initParameter();
initTimer();
addListener();
setSelectedTime(time);
datePickerDialog.show();
}
}else{
canAccess = false;
}
}
}

/**
* 设置日期控件是否显示时和分
*/
public void showSpecificTime(boolean show) {
if (canAccess) {
if (show) {
disScrollUnit();
hour_pv.setVisibility(View.VISIBLE);
hour_text.setVisibility(View.VISIBLE);
minute_pv.setVisibility(View.VISIBLE);
minute_text.setVisibility(View.VISIBLE);
year_pv.setVisibility(View.GONE);
month_pv.setVisibility(View.GONE);
day_pv.setVisibility(View.GONE);
year_text.setVisibility(View.GONE);
month_text.setVisibility(View.GONE);
day_text.setVisibility(View.GONE);
} else {
disScrollUnit(SCROLL_TYPE.HOUR, SCROLL_TYPE.MINUTE);
hour_pv.setVisibility(View.GONE);
hour_text.setVisibility(View.GONE);
minute_pv.setVisibility(View.GONE);
minute_text.setVisibility(View.GONE);
}
}
}

/**
* 设置日期控件是否可以循环滚动
*/
public void setIsLoop(boolean isLoop) {
if (canAccess) {
this.year_pv.setIsLoop(isLoop);
this.month_pv.setIsLoop(isLoop);
this.day_pv.setIsLoop(isLoop);
this.hour_pv.setIsLoop(isLoop);
this.minute_pv.setIsLoop(isLoop);
}
}

/**
* 设置日期控件默认选中的时间
*/
public void setSelectedTime(String time) {
if (canAccess) {
String[] str = time.split(" ");
String[] dateStr = str[0].split("-");

year_pv.setSelected(dateStr[0]);
selectedCalender.set(Calendar.YEAR, Integer.parseInt(dateStr[0]));

month.clear();
int selectedYear = selectedCalender.get(Calendar.YEAR);
if (selectedYear == startYear) {
for (int i = startMonth; i <= MAX_MONTH; i++) {
month.add(formatTimeUnit(i));
}
} else if (selectedYear == endYear) {
for (int i = 1; i <= endMonth; i++) {
month.add(formatTimeUnit(i));
}
} else {
for (int i = 1; i <= MAX_MONTH; i++) {
month.add(formatTimeUnit(i));
}
}
month_pv.setData(month);
month_pv.setSelected(dateStr[1]);
selectedCalender.set(Calendar.MONTH, Integer.parseInt(dateStr[1]) - 1);
executeAnimator(month_pv);

day.clear();
int selectedMonth = selectedCalender.get(Calendar.MONTH) + 1;
if (selectedYear == startYear && selectedMonth == startMonth) {
for (int i = startDay; i <= selectedCalender.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
day.add(formatTimeUnit(i));
}
} else if (selectedYear == endYear && selectedMonth == endMonth) {
for (int i = 1; i <= endDay; i++) {
day.add(formatTimeUnit(i));
}
} else {
for (int i = 1; i <= selectedCalender.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
day.add(formatTimeUnit(i));
}
}
day_pv.setData(day);
day_pv.setSelected(dateStr[2]);
selectedCalender.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateStr[2]));
executeAnimator(day_pv);

if (str.length == 2) {
String[] timeStr = str[1].split(":");

if ((scrollUnits & SCROLL_TYPE.HOUR.value) == SCROLL_TYPE.HOUR.value) {
hour.clear();
int selectedDay = selectedCalender.get(Calendar.DAY_OF_MONTH);
if (selectedYear == startYear && selectedMonth == startMonth && selectedDay == startDay) {
for (int i = startHour; i <= MAX_HOUR; i++) {
hour.add(formatTimeUnit(i));
}
} else if (selectedYear == endYear && selectedMonth == endMonth && selectedDay == endDay) {
for (int i = MIN_HOUR; i <= endHour; i++) {
hour.add(formatTimeUnit(i));
}
} else {
for (int i = MIN_HOUR; i <= MAX_HOUR; i++) {
hour.add(formatTimeUnit(i));
}
}
hour_pv.setData(hour);
hour_pv.setSelected(timeStr[0]);
selectedCalender.set(Calendar.HOUR_OF_DAY, Integer.parseInt(timeStr[0]));
executeAnimator(hour_pv);
}

if ((scrollUnits & SCROLL_TYPE.MINUTE.value) == SCROLL_TYPE.MINUTE.value) {
minute.clear();
int selectedDay = selectedCalender.get(Calendar.DAY_OF_MONTH);
int selectedHour = selectedCalender.get(Calendar.HOUR_OF_DAY);
if (selectedYear == startYear && selectedMonth == startMonth && selectedDay == startDay && selectedHour == startHour) {
for (int i = startMinute; i <= MAX_MINUTE; i++) {
minute.add(formatTimeUnit(i));
}
} else if (selectedYear == endYear && selectedMonth == endMonth && selectedDay == endDay && selectedHour == endHour) {
for (int i = MIN_MINUTE; i <= endMinute; i++) {
minute.add(formatTimeUnit(i));
}
} else {
for (int i = MIN_MINUTE; i <= MAX_MINUTE; i++) {
minute.add(formatTimeUnit(i));
}
}
minute_pv.setData(minute);
minute_pv.setSelected(timeStr[1]);
selectedCalender.set(Calendar.MINUTE, Integer.parseInt(timeStr[1]));
executeAnimator(minute_pv);
}
}
executeScroll();
}
}

private boolean isValidDate(String date, String template) {
boolean convertSuccess = true;
// 指定日期格式
SimpleDateFormat format = new SimpleDateFormat(template, Locale.CHINA);
try {
// 设置lenient为false. 否则SimpleDateFormat会比较宽松地验证日期,比如2015/02/29会被接受,并转换成2015/03/01
format.setLenient(false);
format.parse(date);
} catch (Exception e) {
// 如果throw java.text.ParseException或者NullPointerException,就说明格式不对
convertSuccess = false;
}
return convertSuccess;
}

3.activity中的实现
String timeShow=tvShowTimesAward.getText().toString();
if(timeShow.equals("请设置时间")){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.CHINA);
String now = sdf.format(new Date());
customDatePicker3.show(now);
}else{
// 日期格式为yyyy-MM-dd
customDatePicker3.show("2000-09-08 "+tvShowTimesAward.getText().toString());
}
// 日期格式为yyyy-MM-dd HH:mm

一个完美的时间控件就完成了;

2019-03-01 13:17:09 yuanheng19930119 阅读数 439
  • OpenGL实现Google地图瓦片的绘制漫游视频教程

    OpenGL实现Google地图瓦片的绘制漫游视频培训课程:此次教程所涉及的内容有OpenGL绘制图片、FreeImage加载图片、墨卡托投影、瓦片的金字塔模型、FramebufferObject、地图的移动和缩放优化,采用屏幕瓦片绘制优化、采用ImageBuffer优化、采用多线程优化、地图操作优化、模拟,生成全球瓦片(debug)、MFC-对话框中绘制地图、MFC-View中绘制地图、QT中绘制地图、在线浏览Google地图等。

    8876 人正在学习 去看看 张立铜

在这篇博客中主要讲解给Android自定义控件添加点击事件,实现可以按住百分比圆圈在屏幕上进行拖动圆圈的功能。分两部分讲,第一部分是获取自定义控件的坐标,第二部分是重新绘制控件。

没有看过自定义控件的可以先阅读以下这篇博客:

https://blog.csdn.net/yuanheng19930119/article/details/88055225

第一部分:获取自定义控件坐标

首先看一张图,这是自定义控件中获取坐标的函数,各函数获取的坐标含义如下所示:

1.view获取自身坐标:getLeft(),getTop(),getRight(),getBottom()

getTop:获取到的,是view自身的顶边到其父布局顶边的距离

getLeft:获取到的,是view自身的左边到其父布局左边的距离

getRight:获取到的,是view自身的右边到其父布局左边的距离

getBottom:获取到的,是view自身的底边到其父布局顶边的距离

2.view获取自身宽高:getHeight(),getWidth()

3.motionEvent获取坐标:getX(),getY(),getRawX(),getRawY()

getX():获取点击事件相对控件左边的x轴坐标,即点击事件距离控件左边的距离

getY():获取点击事件相对控件顶边的y轴坐标,即点击事件距离控件顶边的距离

getRawX():获取点击事件相对整个屏幕左边的x轴坐标,即点击事件距离整个屏幕左边的距离

getRawY():获取点击事件相对整个屏幕顶边的y轴坐标,即点击事件距离整个屏幕顶边的距离

第二部分:计算控件左上角坐标,当按下控件移动的时候,计算偏移距离,重新绘制,代码及效果图如下所示:

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
 
public class SimpleView extends View {
 
    private final static String TAG = SimpleView.class.getSimpleName();
    //画笔
    private Paint mPaint;
    private RectF oval;
    //事件处理
    private EventHandle mEventHandle;
    //鼠标按下位置
    private int startX,startY;
    //按下鼠标时控件的位置
    private int startLeft,startTop;
    //状态栏高度
    int statusHeight = 0;
    public SimpleView(Context context) {
        super(context);
        init();
    }
 
    public SimpleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }
 
    public SimpleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
 
    private void init(){
        mPaint = new Paint();
        //设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(30.0f);
        oval=new RectF();
        mEventHandle=null;
        startY=startX=0;
 
        int resourceId = this.getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            statusHeight = this.getResources().getDimensionPixelSize(resourceId);
        }
    }
 
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        Log.e(TAG, "onMeasure--widthMode-->" + widthMode);
        switch (widthMode) {
            case MeasureSpec.EXACTLY:
                //精确值模式,当控件的layout_width和layout_height属性指定为具体数值或match_parent时。
                break;
            case MeasureSpec.AT_MOST:
                //最大值模式,当空间的宽高设置为wrap_content时。
                break;
            case MeasureSpec.UNSPECIFIED:
                //未指定模式,View想多大就多大,通常在绘制自定义View时才会用。
                break;
        }
        //取最小边为控件的宽高的最小值
        int minWidth=widthSize>heightSize?heightSize:widthSize;
        setMeasuredDimension(minWidth,minWidth);
    }
 
 
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPaint.setColor(Color.GRAY);
        // FILL填充, STROKE描边,FILL_AND_STROKE填充和描边
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        int with = getWidth();
        int height = getHeight();
        Log.e(TAG, "onDraw---->" + with + "*" + height);
        float radius = with / 2-5;
        canvas.drawCircle(with / 2, with / 2, radius, mPaint);
        mPaint.setColor(Color.RED);
        oval.set(with / 2 - radius, with / 2 - radius, with / 2
                + radius, with / 2 + radius);//用于定义的圆弧的形状和大小的界限
        int sweepAngle=120;
        canvas.drawArc(oval, 0, -sweepAngle, true, mPaint);  //根据进度画圆弧
        double percent=sweepAngle/360.0;
        //设置文本颜色
        mPaint.setColor(Color.WHITE);
        //绘制文本百分比数据
        canvas.drawText(String.format("%.2f",percent)+"%",(float)(with/2+radius*Math.cos(sweepAngle*Math.PI/360)/4)
                ,(float)(with/2-radius*Math.sin(sweepAngle*Math.PI/360)/3),mPaint);
        canvas.drawText(String.format("%.2f",1-percent)+"%",(float)(with/2-radius*Math.cos(sweepAngle*Math.PI/360))
                ,(float)(with/2+radius*Math.sin(sweepAngle*Math.PI/360)/3),mPaint);
    }
 
    @Override
    public boolean onTouchEvent(MotionEvent event) {
 
 
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
 
                startX=(int)event.getRawX();
                startY=(int)event.getRawY();
                startLeft=(int)(startX-event.getX());
                /**
                 * 这里startTop计算有些偏离,原因在于计算时加入了标题栏和状态栏的高度
                 * 注意:要是你的Activity没有去掉标题栏,这里还要去掉标题栏的高度
                 */
                startTop= (int)(startY-event.getY())-statusHeight;//减去状态栏高度
                break;
            case MotionEvent.ACTION_MOVE:
                if(mEventHandle!=null)
                {
                    mEventHandle.onTouchEvent(event);
                }else{
                    int disX=(int)event.getRawX()-startX;//计算偏移的X坐标
                    int disY=(int)event.getRawY()-startY;//计算偏移的Y坐标;
                    int left=startLeft+disX;
                    int top=startTop+disY;
                    //更新控件位置
                    layout(left,top,left+getWidth(),top+getHeight());
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        //返回true表示不消耗此事件,事件继续传递,返回flase表示事件消耗
        return true;
    }
 
    public void setmEventHandle(EventHandle mEventHandle) {
        this.mEventHandle = mEventHandle;
    }
 
    interface EventHandle{
        public void onTouchEvent(MotionEvent event);
    }
}


布局文件xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    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="com.example.myapplication.MainActivity">
    <com.example.myapplication.SimpleView
        android:layout_width="150dp"
        android:layout_height="150dp"
        />
 
</RelativeLayout>

MainActivity,要注意的是,需要在这里将标题栏去掉,否则拖动的时候会出现偏差

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Window;
 
 
public class MainActivity extends AppCompatActivity{
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);//去掉标题栏
        /*
            requestWindowFeature(Window.FEATURE_NO_TITLE)无效解决方法:
            正常情况下requestWindowFeature(Window.FEATURE_NO_TITLE)是可以生效的,
            但是当Activity继承子AppCompatActivity的时候,这个就失效了 
            解决办法:
            1、手动在oncreate里调用hide()
            if (getSupportActionBar() != null){
               getSupportActionBar().hide();
            }
         */
        if (getSupportActionBar() != null){
            getSupportActionBar().hide();
        }
        setContentView(R.layout.activity_main);
    }
 
}

效果图如下所示:

2016-09-17 11:56:29 Airsaid 阅读数 3834
  • OpenGL实现Google地图瓦片的绘制漫游视频教程

    OpenGL实现Google地图瓦片的绘制漫游视频培训课程:此次教程所涉及的内容有OpenGL绘制图片、FreeImage加载图片、墨卡托投影、瓦片的金字塔模型、FramebufferObject、地图的移动和缩放优化,采用屏幕瓦片绘制优化、采用ImageBuffer优化、采用多线程优化、地图操作优化、模拟,生成全球瓦片(debug)、MFC-对话框中绘制地图、MFC-View中绘制地图、QT中绘制地图、在线浏览Google地图等。

    8876 人正在学习 去看看 张立铜

转载请标明出处: http://blog.csdn.net/airsaid/article/details/52562488
本文出自:周游的博客

前言

距离写上一篇自定义View文章已经大半年过去了,一直想继续写,但是无奈技术有限,生怕误人子弟。这段时间项目刚刚完成,有点时间,跟着大神的脚步,巩固下自定义View的相关基础知识。

Canvas&Paint

Canvas和Paint可以理解为现实中的画布和画笔,这两样是绘图必备,首先来详细的了解下这两个。

Paint常用函数

首先来看下Paint的常用函数:
* setColor(int color):设置画笔颜色。
* setStrokeWidth(float width):设置画笔宽度。
* setAntiAlias(boolean aa):设置抗锯齿。
* setStyle(Style style): 设置填充样式。
* setShadowLayer(float radius, float dx, float dy, int shadowColor):设置阴影。

设置画笔颜色和画笔宽度没什么可说的,设置抗锯齿可以让我们绘制的图形更加圆滑,除了Paint的setAntiAlias()函数可以设置抗锯齿外,也可以通过Canvas设置:

canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG)); 

setStyle()

setStyle()函数可以设置如下参数:
* Paint.Style.STROKE:描边。
* Paint.Style.FILL:填充内部。
* Paint.Style.FILL_AND_STROKE:填充内部和描边。
新建个项目,在onDraw函数中分别绘制下,看下都有什么区别:

protected void onDraw(Canvas canvas) {
    mPaint.setColor(Color.BLUE);
    mPaint.setStyle(Paint.Style.STROKE);
    canvas.drawCircle(mLeftX, mLeftY, 100, mPaint);

    mPaint.setColor(Color.RED);
    mPaint.setStyle(Paint.Style.FILL);
    canvas.drawCircle(mLeftX * 4, mLeftY, 100, mPaint);

    mPaint.setColor(Color.BLACK);
    mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
    canvas.drawCircle(mLeftX * 7, mLeftY, 100, mPaint);
}

运行结果:
这里写图片描述

通过运行结果可以看到,貌似Paint.Style.FILL和Paint.Style.FILL_AND_STROKE其实并没有什么区别。
但是,如果把画笔的宽度调宽一些:

mPaint.setStrokeWidth(50);

mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(mLeftX, mLeftY, 50, mPaint);

mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(mLeftX * 4, mLeftY, 50, mPaint);

运行结果:
这里写图片描述

可以看出看来,FILL_AND_STROKE其实会把宽度一起填充。

setShadowLayer()

setShadowLayer(float radius, float dx, float dy, int shadowColor):
* radius:阴影倾斜度。
* dx:水平位移。
* dy:垂直位移。
* shadowColor:阴影颜色。
给文字绘制上阴影:

mPaint.setTextSize(50);
mPaint.setColor(Color.BLUE);
mPaint.setShadowLayer(5, 10, 10, Color.GREEN);
canvas.drawText("Airsaid", mLeftX, mLeftY, mPaint);

运行结果:
这里写图片描述

Canvas常用函数

Canvas作为画布,含有绘制各种图形的函数,下面根据具体图形来分类进行详细讲解。

绘制背景

  • drawColor(int):绘制画布背景。
  • drawRGB(int r, int g, int b):同上。
  • drawARGB(int a, int r, int g, int b):同上,四个参数取值范围0~255。

绘制一条直线

  • drawLine(float startX, float startY,float stopX,float stopY,Paint paint):
    • startX:开始x坐标。
    • startY:开始Y坐标。
    • stopX:结束x坐标。
    • stopY:结束Y坐标。
    • paint:绘制直线所用画笔。
      代码实例:
mPaint.setColor(Color.BLUE);
mPaint.setStrokeWidth(10);
canvas.drawLine(0, 0, getWidth(), 0, mPaint);

运行结果:
这里写图片描述

绘制多条直线

  • drawLines(float[] pts, Paint paint):一般绘制多条直线。
    • pts:坐标点数据的集合,每4个为一组绘制一条直线。
    • paint:绘制直线所用画笔。
      代码实例:
mPaint.setColor(Color.BLUE);
mPaint.setStrokeWidth(10);
float pts[] = {0, 10, getWidth(), 10, 0, 50, getWidth(), 50};
canvas.drawLines(pts, mPaint);

运行结果:
这里写图片描述

  • drawLines(float[] pts, int offset, int count, Paint paint):有选择的绘制多条直线。
    • pts:坐标点数据的集合,每4个为一组绘制一条直线。
    • offset:跳过的数据个数,跳过的数据将不参与绘制过程。
    • conunt:实际参与绘制的数据个数。
    • paint:绘制直线所用画笔。
      代码示例,跳过一条线的绘制:
mPaint.setColor(Color.BLUE);
mPaint.setStrokeWidth(10);
float pts[] = {0, 10, getWidth(), 10, 0, 50, getWidth(), 50};
canvas.drawLines(pts, 4, 4, mPaint);

运行结果:
这里写图片描述

绘制单点

  • drawPoint(float x, float y,Paint paint):绘制一个点。
    • x:点的x坐标。
    • y:点的y坐标。
    • paint:绘制点所用画笔。
      代码示例:
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(20);
canvas.drawPoint(100, 100, mPaint);

运行结果:
这里写图片描述

绘制多点

  • drawPoints(float[] pts, Paint paint):绘制多个点。
    • pts:坐标点的数据集合,每两个为一组绘制一个点。
      代码示例(绘制三个点):
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(20);
float pts[] = {100, 100, 200, 100, 300, 100};
canvas.drawPoints(pts, mPaint);

运行结果:
这里写图片描述

  • drawPoints(float[] pts, int offset, int count, Paint paint): 有选择的绘制多个点。
    • pts:参数同上,其他参数同绘制多条线一样。
      代码示例(跳过第1个点,只绘制2个点):
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(20);
float pts[] = {100, 100, 200, 100, 300, 100};
canvas.drawPoints(pts, 2, 4, mPaint);

运行结果:

这里写图片描述

绘制矩形

  • drawRect(Rect r, Paint paint):根据传入的Rect绘制矩形。
  • drawRect(RectF rect, Paint paint):根据传入的RectF绘制矩形。
  • drawRect(float left, float top, float rigth, float bottom, Paint paint):直接传入矩形的四个点来绘制矩形。
    代码示例(分别使用以上函数绘制三个矩形):
mPaint.setColor(Color.RED);

Rect rect = new Rect(100, 30, 200, 100);
canvas.drawRect(rect, mPaint);

RectF rectF = new RectF(300, 30, 400, 100);
canvas.drawRect(rectF, mPaint);

canvas.drawRect(500, 30, 600, 100, mPaint);

其中Rect和RectF为矩形辅助类,可根据4个点构建一片矩形区域,用于帮助我们对矩形进行操作。

运行结果:
这里写图片描述

绘制圆角矩形

  • drawRoundRect(RectF rect, float rx, float ry, Paint paint): 根据传入的RectF绘制圆角矩形。
    • rect:要绘制的矩形。
    • rx:x轴圆角椭圆半径。
    • ry:y轴圆角椭圆半径。
  • drawRoundRect(float left, float top, float rigth, float bottom, float rx, float ry, Paint paint):直接传入矩形的四个点来绘制圆角矩形,从API21开始提供。
    参数同上。
    代码实例(根据以上函数分别绘制圆角矩形):
mPaint.setColor(Color.RED);
RectF rectF = new RectF(0, 0, 300, 100);
canvas.drawRoundRect(rectF, 30f, 30f, mPaint);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    canvas.drawRoundRect(400f, 0f, 700f, 100f, 30f, 30f, mPaint);
}

其中,由于直接传入矩形的四个点来绘制圆角矩形的函数是在API21才开始提供,所以进行了判断。
运行结果:
这里写图片描述

绘制圆形

  • drawCircle(float cx,float cy,float radius,Paint paint):
    • cx:圆心点x轴坐标。
    • cy:圆心点y轴坐标。
    • radius:圆的半径。
    • paint:绘制圆形所用画笔。
      代码示例:
mPaint.setColor(Color.RED);
canvas.drawCircle(100f, 50f, 50f, mPaint);

运行结果:
这里写图片描述

绘制椭圆

  • drawOval(RectF oval, Paint paint):根据矩形对象绘制椭圆。
    • oval:矩形对象。椭圆根据该矩形对象生成,以矩形的长作为椭圆的x轴,宽为y轴。
  • drawOval(float left, float top, float rigth, float bottom, Paint paint):直接传入矩形的四个点来绘制椭圆,从API21开始提供。
    参数同上。
    代码示例:
mPaint.setColor(Color.RED);
RectF rectF = new RectF(50, 0, 200, 100);
canvas.drawOval(rectF, mPaint);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    canvas.drawOval(300, 0, 450, 100, mPaint);
}

运行结果:
这里写图片描述

绘制弧形

  • drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint):
    • oval:矩形对象。
    • startAngle:弧形开始的角度。
    • sweepAngle:弧形持续的角度。
    • useCenter:是否有弧形的两边。
    • paint:绘制弧形的画笔。
  • drawArc(float left, float top, float rigth, float bottom,float startAngle, float sweepAngle, boolean useCenter, Paint paint):
    参数同上。
    代码实例:
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(5);
mPaint.setStyle(Paint.Style.STROKE);
RectF rectF = new RectF(50, 0, 200, 100);
canvas.drawArc(rectF, 0f, 180f, false, mPaint);

RectF rectF2 = new RectF(250, 0, 400, 100);
canvas.drawArc(rectF2, 0f, 180f, true, mPaint);

RectF rectF3 = new RectF(450, 0, 600, 100);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawArc(rectF3, 0f, 180f, true, mPaint);

运行结果:
这里写图片描述

参考链接

2016-05-20 15:57:26 IT_ZJYANG 阅读数 773
  • OpenGL实现Google地图瓦片的绘制漫游视频教程

    OpenGL实现Google地图瓦片的绘制漫游视频培训课程:此次教程所涉及的内容有OpenGL绘制图片、FreeImage加载图片、墨卡托投影、瓦片的金字塔模型、FramebufferObject、地图的移动和缩放优化,采用屏幕瓦片绘制优化、采用ImageBuffer优化、采用多线程优化、地图操作优化、模拟,生成全球瓦片(debug)、MFC-对话框中绘制地图、MFC-View中绘制地图、QT中绘制地图、在线浏览Google地图等。

    8876 人正在学习 去看看 张立铜

Android自定义主要有3种,自定义View、自定义ViewGroup、继承重写系统控件

本文主要讲解Android中如何自定义View

Android打造自定义控件,大体的思路主要有以下五点:

1.创建自定义属性,在res/values目录下创建attrs.xml文件,声明自定义控件的属性

2.创建自定义View类,继承于View类,重写View的三个构造方法

3.通过TypeArray获得各个自定义属性,并将Paint设置为这些属性的内容

4.重写onMeasure方法,设置好视图在界面上所显示的大小

5.重写onDraw方法,通过paint和canvas渲染出自定义控件




1.创建自定义属性,在res/values目录下创建attrs.xml文件,声明自定义控件的属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
    <declare-styleable name="myTextView">
        <attr name="mytextContent" format="string" type="string"></attr>
	<attr name="mytextColor" format="color" type="color"></attr>
	<attr name="mytextSize" format="dimension" type="dimension"></attr>
    </declare-styleable>
      
</resources>


声明自定义属性有两个作用,一方面可以让我们在布局文件中直接使用这些属性,另一方面,在xml中声明属性之后会在R类中生成对应的资源ID,方便到时候TypeArray的使用(关于TypeArray见下文)
每个attr标签表示声明一个属性,name是属性的名字,format是属性的格式,比如string表示这个属性必须为字符串,color表示这个属性必须为颜色类型,dimension表示这个属性必须为像素即dp、sp之类的



2.创建自定义View类,继承于View类,重写View的三个构造方法

public class MyTextView extends View implements View.OnClickListener{
	
	private String mytextContent;
	
	private int mytextColor;
	
	private int mytextSize;
	//画笔,用于绘制图形时使用
	private Paint paint;
	//矩形对象,用于计算文字位置时使用
	private Rect rect;
	
	public MyTextView(Context context) {
		// TODO Auto-generated constructor stub
		this(context,null);
	}
	
	public MyTextView(Context context, AttributeSet attrs) {
		// TODO Auto-generated constructor stub
		this(context,attrs,0);
		
	}

	public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		// TODO Auto-generated constructor stub
	}	

}


可以看到三个构造方法的区别在于参数
public MyTextView(Context context)   【通过传入上下文对象来创建view】

public MyTextView(Context context, AttributeSet attrs)   【AttributeSet类型是用来获得我们声明的各个属性,当我们在xml布局文件中声明自定义View时,就会调用此构造方法】

public MyTextView(Context context, AttributeSet attrs, int defStyleAttr)   【多了一个defStyleAttr属性,当我们需要为view设定style时才会用到】 

另外,我们对前两个构造方法都调用了this(),学过java的都知道,this表示调用本类的构造方法,在第一个构造方法中,我们调用了 this(context,null);其实是调用了第二个构造方法,在第二个构造方法中调用了this(context,attrs,0);其实是调用了第三个构造方法,所以这样写的好处是无论我们使用哪个构造方法,最终都会进到第三个构造方法,所以接下来我们要做的便是在第三个构造方法中获得我们的属性



3.通过TypeArray获得各个自定义属性,并将Paint设置为这些属性的内容

public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
	super(context, attrs, defStyleAttr);
	// TODO Auto-generated constructor stub
	TypedArray array = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.myTextView, defStyleAttr, 0);
	//获得属性,并设置默认值
	mytextContent = array.getString(R.styleable.myTextView_mytextContent);
	mytextColor = array.getColor(R.styleable.myTextView_mytextColor,Color.WHITE);
	mytextSize = array.getDimensionPixelSize(R.styleable.myTextView_mytextSize, 30);
	
	array.recycle();
		
	paint = new Paint();
	//将画笔的文字大小设置为我们定义的大小
	paint.setTextSize(mytextSize);
	rect = new Rect();
	/**
	* 此方法可以获得文字所在的矩形区域,并赋给rect
	* 参数1:传入文字的内容
	* 参数2:传入文字起始的长度,一般为0
	* 参数3:传入文字结束的长度,一般为text.length
	* 参数4:传入一个Rect矩形对象
	*/
	paint.getTextBounds(mytextContent, 0, mytextContent.length(), rect);
}


刚才在上文中已说过,此构造方法的AttributeSet参数可以得到我们在attr中声明的属性,那为什么此处还要通过TypeArray来获得呢?TypeArray有什么用?其实如果我们是直接通过AttributeSet获得属性的话,还需要解析才能获得我们需要的各个属性的值以及格式,而TypeArray帮我们把这些操作都封装好了,所以通过TypeArray可以很方便的获取到我们的属性



4.重写onMeasure方法,设置好视图在界面上所显示的大小

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	// TODO Auto-generated method stub
	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	//setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}


对这个方法的使用见下文



5.重写onDraw方法,通过paint和canvas渲染出自定义控件

@Override
protected void onDraw(Canvas canvas) {
	// TODO Auto-generated method stub
	paint.setColor(Color.BLACK);
	//canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint);
	/**
	* 参数1:圆心的横坐标
	* 参数2:圆心的纵坐标
	* 参数3:圆的半径
	* 参数4:用来绘制的画笔
	*/
	canvas.drawCircle(getMeasuredWidth()/2, getMeasuredHeight()/2, getMeasuredWidth()/2, paint);
	paint.setColor(Color.RED);
	canvas.drawCircle(getMeasuredWidth()/2, getMeasuredHeight()/2, getMeasuredWidth()/3, paint);
		
	paint.setColor(mytextColor);
	canvas.drawText(mytextContent, getWidth() / 2 - rect.width() / 2, getHeight() / 2 + rect.height() / 2, paint);		
}


我们在这里通过canvas调用了两次drawCircle,即绘制了两个圆形,注意到第二个圆形的半径为getMeasuredWidth()/3,比第一个圆的半径小了,而圆心又与第一个圆一致,所以待会儿绘制出来的效果就是一个小圆叠在一个大圆前面,在每次绘制圆之前都调用了paint设置颜色,是为了两个圆形的颜色不一样。
最后再次设置paint颜色为mytextColor,即我们的文字内容的颜色,然后通过canvas.drawText进行文字的绘制,这里之所以getWidth() / 2 - rect.width() / 2是因为将View的宽度的一半减去文字内容的宽度的一半,得到的就是文字内容的左上角的横坐标(可以自行画图理解),纵坐标也是同理。



至此,我们完成了基本的定义,接下来就是在布局文件中使用它了:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:mytext="http://schemas.android.com/apk/res/com.example.myview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="${relativePackage}.${activityClass}" >

    <com.example.view.MyTextView 
        android:layout_width="200dp"
        android:layout_height="200dp"
        mytext:mytextContent="1"
        mytext:mytextColor="#FFF"
        mytext:mytextSize="20sp"    
        />

</RelativeLayout>


xmlns:mytext="http://schemas.android.com/apk/res/com.example.myview"是命名空间的声明,等下我们声明自定义属性的时候需要用到,这里的路径是http://scheam.....res/+项目包名


运行



运行之后,发现在屏幕成功出现了一个圆形背景中间带有文字内容,这里我们设置的宽和高都是200dp,如果将layout_width和layout_height都设置为wrap_content,会发现它并不像我们平时那样会自动有一个默认大小,而是像fill_parent一样的填充屏幕,很明显这不是我们愿意看到的,这个时候就需要用到onMeasure方法了:

首先为我们的自定义类加上两个私有成员标量:

//wrap_content时默认的宽度
private final int DEFAULT_WIDTH = 200;
//wrap_content时默认的高度
private final int DEFAULT_HEIGHT = 200;


重写onMeasure方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	// TODO Auto-generated method stub
	//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		
	int width;  //最终的宽度
	int height;   //最终的高度
		
	int wspecMode = MeasureSpec.getMode(widthMeasureSpec);
	int wspecSize = MeasureSpec.getSize(widthMeasureSpec);
	int hspecMode = MeasureSpec.getMode(heightMeasureSpec);
	int hspecSize = MeasureSpec.getSize(heightMeasureSpec);
		
	if(wspecMode==MeasureSpec.EXACTLY){
		width = wspecSize;
	}
	else{
		width = DEFAULT_WIDTH;
	}
	if(hspecMode==MeasureSpec.EXACTLY){
		height = hspecSize;
	}
	else{
		height = DEFAULT_HEIGHT;
	}
	setMeasuredDimension(width, height);
}


其中,有两个关键的方法,getModegetSize,getSize是获得用户所设定的view的宽高,getMode是获得这个属性的模式,Mode有三种
MeasureSpec.EXACTLY【设置了明确的值或者是MATCH_PARENT】

MeasureSpec.AT_MOST【表示子布局限制在一个最大值内,一般为WARP_CONTENT】

MeasureSpec.UNSPECIFIED【表示子布局想要多大就多大,很少使用】

对模式进行判断,如果模式是EXACTLY,说明用户在布局文件中对宽高设定了具体数值或者match_parent,如果是具体数值,那么就将这个具体数值作为最终宽高,如果是match_parent,就将其填充父布局。如果是AT_MOST,说明用户在布局文件中设定为了wrap_content,那么就应该将我们的默认宽度或者高度作为最终的宽高
通过以上测量,就能够在设定为wrap_content时依然能够限定在一定的大小中。


另外,如果想为自定义控件加上一些点击事件之类的效果,我们还可以:

1.让自定义类实现OnClickListener



2.在构造方法中注册监听事件



3.实现onClick方法



运行并点击,就会发现文字内容每点击一次就+1


希望本文能够帮助你解决自定义View的困惑,关于ViewGroup和重写系统控件的见解会在以后的博文进行整理

Android自定义控件

阅读数 1509

没有更多推荐了,返回首页