1.使用绘制方法,画出一个view,然后在xml文件中使用即可
1.创建自定义的view
这里我只画了一个空心圆,canvas是画布,paint是画笔,用画笔可以画出任何图形设置颜色、空心实心、线条宽度,通过RectF设置圆的位置和大小
public class PaintView extends View {
public PaintView(Context context) {
super(context);
}
public PaintView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
RectF rectF = new RectF(10, 10, 100, 100);
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.RED);
paint.setStrokeWidth(10);
canvas.drawOval(rectF, paint);
}
}
2.将画好的view引用到xml中
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.mazhan.view.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.mazhan.view.PaintView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
这样我们绘制的view就可以被加载出来了
Snip20201022_18.png
2.绘制线条时的注意点
1.需要设置stroke
2.二阶的贝塞尔曲线可以设置跟随手势画出任意的图形,这个在线教育中使用的画板就是使用的二阶贝塞尔曲线
public class CanvasPathView extends View {
public CanvasPathView(Context context) {
super(context);
}
public CanvasPathView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(10);
// 如果需要绘制path的话,需要设置paint的style为stroke
paint.setStyle(Paint.Style.STROKE);
// 线条需要通过path来绘制
Path path = new Path();
// 设置起点
path.moveTo(50,50);
// 设置连接点
path.lineTo(100,100);
//二阶贝塞尔曲线
path.quadTo(200,200,300,100);
canvas.drawPath(path, paint);
}
}
Snip20201022_20.png
3.移动坐标系
默认的坐标系是(0,0),可以通过设置canvas的属性来平移或者是旋转坐标系,然后将坐标系的模式保存下来,这样可以便利的设置一些特殊的坐标系
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.RED);
paint.setStrokeWidth(10);
// 设置坐标系起始点为100,100
canvas.translate(100,100);
// 保存设置好的坐标系
canvas.save();
// 设置坐标系旋转
canvas.rotate(90);
// 销毁坐标系,还原上一次保存的坐标系
canvas.restore();
canvas.drawLine(0,0,100,0,paint);
}
4.自定义控件的三大方法执行顺序
public class MyFrameLayout extends FrameLayout {
public MyFrameLayout(@NonNull Context context) {
super(context);
}
public MyFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d(getClass().getSimpleName(), "onDraw: " + "MyFrameLayout父控件");
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d(getClass().getSimpleName(), "onMeasure: " + "MyFrameLayout父控件");
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Log.d(getClass().getSimpleName(), "onLayout: " + "MyFrameLayout父控件");
}
}
public class MyView extends View {
public MyView(Context context) {
super(context);
}
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setTextSize(20);
canvas.drawText("这是一个view", 10, 10, paint);
Log.d(getClass().getSimpleName(), "onDraw: " + "MyView子控件");
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d(getClass().getSimpleName(), "onMeasure: " + "MyView子控件");
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Log.d(getClass().getSimpleName(), "onLayout: " + "MyView子控件");
}
}
在xml中使用MyFrameLayout和MyView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.mazhan.customviewmethodorder.MainActivity">
<com.mazhan.customviewmethodorder.MyFrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.mazhan.customviewmethodorder.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</com.mazhan.customviewmethodorder.MyFrameLayout>
</LinearLayout>
10-22 07:20:51.088 6735-6735/com.mazhan.customviewmethodorder D/MyFrameLayout: onMeasure: MyFrameLayout父控件
10-22 07:20:51.150 6735-6735/com.mazhan.customviewmethodorder D/MyFrameLayout: onMeasure: MyFrameLayout父控件
10-22 07:20:51.153 6735-6735/com.mazhan.customviewmethodorder D/MyFrameLayout: onLayout: MyFrameLayout父控件
10-22 07:28:38.713 6928-6928/com.mazhan.customviewmethodorder D/MyFrameLayout: onMeasure: MyFrameLayout父控件
10-22 07:28:38.753 6928-6928/com.mazhan.customviewmethodorder D/MyFrameLayout: onMeasure: MyFrameLayout父控件
10-22 07:28:38.760 6928-6928/com.mazhan.customviewmethodorder D/MyFrameLayout: onLayout: MyFrameLayout父控件
10-22 07:30:32.941 7079-7079/com.mazhan.customviewmethodorder D/MyView: onMeasure: MyView子控件
10-22 07:30:32.943 7079-7079/com.mazhan.customviewmethodorder D/MyFrameLayout: onMeasure: MyFrameLayout父控件
10-22 07:30:32.995 7079-7079/com.mazhan.customviewmethodorder D/MyView: onMeasure: MyView子控件
10-22 07:30:32.995 7079-7079/com.mazhan.customviewmethodorder D/MyFrameLayout: onMeasure: MyFrameLayout父控件
10-22 07:30:32.996 7079-7079/com.mazhan.customviewmethodorder D/MyView: onLayout: MyView子控件
10-22 07:30:32.996 7079-7079/com.mazhan.customviewmethodorder D/MyFrameLayout: onLayout: MyFrameLayout父控件
10-22 07:30:33.188 7079-7079/com.mazhan.customviewmethodorder D/MyView: onDraw: MyView子控件
5.VierPager的使用
1.在MainActivity的xml中添加ViewPager
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" tools:context="com.mazhan.viewpagerdemo.MainActivity">
<android.support.v4.view.ViewPager
android:id="@+id/viewPagerId"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_purple">
</android.support.v4.view.ViewPager>
</LinearLayout>
2.给ViewPager设置PagerAdapter
public class MainActivity extends AppCompatActivity {
private ArrayList<TextView> mDatas;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
initViews();
}
private void initViews() {
ViewPager viewPager = (ViewPager) findViewById(R.id.viewPagerId);
viewPager.setAdapter(new PagerAdapter() {
@Override
public int getCount() {
return mDatas.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
TextView textView = mDatas.get(position);
container.addView(textView);
return textView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
});
}
private void initData() {
mDatas = new ArrayList<>();
for (int i = 0; i < 10; i++) {
TextView textView = new TextView(getApplicationContext());
textView.setTextSize(20);
textView.setText("这是第" + i + "个页面");
mDatas.add(textView);
}
}
}
6.ViewFliper的使用
使用viewFlipper完成文字向上滚动的效果(只要设置滚动的动画即可)
在xml中直接使用系统的ViewFlipper即可
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
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.mazhan.viewfliperdemo.MainActivity">
<ViewFlipper
android:id="@+id/ViewFlipperId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/myColorRed">
</ViewFlipper>
</android.support.constraint.ConstraintLayout>
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
ViewFlipper viewFlipper = (ViewFlipper) findViewById(R.id.ViewFlipperId);
viewFlipper.startFlipping();
//设置viewFlipper的一些属性
viewFlipper.setFlipInterval(5000);
// 设置viewFlipper的的入场动画和离场动画
// 组合动画 透明度 位移
// 离场动画
AnimationSet inAnimationSet = new AnimationSet(false);
AnimationSet outAnimationSet = new AnimationSet(false);
AlphaAnimation inAlphaAnimation = new AlphaAnimation(0, 1);
TranslateAnimation inTranslateAnimation = new TranslateAnimation(
TranslateAnimation.RELATIVE_TO_SELF, 0,
TranslateAnimation.RELATIVE_TO_SELF, 0,
TranslateAnimation.RELATIVE_TO_SELF, 1,
TranslateAnimation.RELATIVE_TO_SELF, 0);
inAnimationSet.addAnimation(inAlphaAnimation);
inAnimationSet.addAnimation(inTranslateAnimation);
inAnimationSet.setDuration(3000);
AlphaAnimation outAlphaAnimation = new AlphaAnimation(1, 0);
TranslateAnimation outTranslateAnimation = new TranslateAnimation(
TranslateAnimation.RELATIVE_TO_SELF, 0,
TranslateAnimation.RELATIVE_TO_SELF, 0,
TranslateAnimation.RELATIVE_TO_SELF, 0,
TranslateAnimation.RELATIVE_TO_SELF, -1);
outAnimationSet.addAnimation(outAlphaAnimation);
outAnimationSet.addAnimation(outTranslateAnimation);
outAnimationSet.setDuration(2000);
viewFlipper.setInAnimation(inAnimationSet);
viewFlipper.setOutAnimation(outAnimationSet);
//向ViewFlipper中添加要滚动的view
for (int i = 0; i < 10; i++) {
TextView textView = new TextView(getApplicationContext());
textView.setText("这是第" + i + "个textView");
viewFlipper.addView(textView);
}
}
}
7.自定义控件自定义属性
1.比如说自定义一个只滚动text的ViewFlipper,叫做textViewFlipper。如果要给这个控件添加自定义属性,那么需要新建一个文件
image.png
2.那么如何在xml中使用自定义的属性呢
image.png
3.这样在自定义的控件类被加载的时候,就会调用构造方法,那么就可以得到自定义的属性名和属性值
image.png
4.将自定义的属性设置给textView
这里自定义属性的获取方式并不是这样遍历的,是直接通过字段获取的,R.styleable,R.styleable.TextViewFlipper_fontColor,R.styleable.TextViewFlipper_fontSize,这些都是系统根据自定义属性的文件生成的。
注意点:这里获取属性的时机是要注意的,只能在构造方法里进行获取,只有在构造方法被调用的时候,attrs的值才是正确的,当构造方法执行完成之后,attrs的值会发生变化,也就得不到自定义的属性了。
public class TextViewFlipper extends ViewFlipper {
private AttributeSet mAttrs;
private Context mContext;
private float mTextSize;
private int mTextColor;
public TextViewFlipper(Context context) {
super(context);
}
public TextViewFlipper(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mAttrs = attrs;
int attributeCount = attrs.getAttributeCount();
for (int i = 0; i < attributeCount; i++) {
String attributeName = attrs.getAttributeName(i);
String attributeValue = attrs.getAttributeValue(i);
Log.d(getClass().getSimpleName(), "TextFlipperView: attributeName: " + attributeName
+" attributeValue: "+attributeValue);
}
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TextViewFlipper);
mTextColor = typedArray.getColor(R.styleable.TextViewFlipper_fontColor, Color.BLACK);
mTextSize = typedArray.getDimension(R.styleable.TextViewFlipper_fontSize, 100);
//销毁typedArray
typedArray.recycle();
}
void setViews (ArrayList<TextView> textViews) {
for (int i = 0; i < textViews.size(); i++) {
TextView textView = textViews.get(i);
textView.setTextColor(mTextColor);
textView.setTextSize(mTextSize);
addView(textView);
}
}
}
8.制作垂直滑动的滑块demo
自定义VerticalSlideView,里面放一个ImageView,让imageView跟随手指拖动而滑动
public class VerticalSlideView extends ViewGroup {
private View mChildView;
private float mStartY;
private float mStartX;
public VerticalSlideView(Context context) {
super(context);
}
public VerticalSlideView(Context context, AttributeSet attrs) {
super(context, attrs);
}
// 确定控件的大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mChildView = getChildAt(0);
//1. 获得mode和size
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int withSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// 测量自控件的宽度
measureChildren(widthMeasureSpec, heightMeasureSpec);
// 2.判断mode,根据mode来设置size
int childWidth = mChildView.getMeasuredWidth();
// 设置自身控件的大小
setMeasuredDimension(childWidth, heightSize - dip2px(10));
}
// 对子控件进行布局
int mTop = 0;
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 位置:左上右下
mChildView.layout(0, mTop, mChildView.getMeasuredWidth(), mTop+mChildView.getMeasuredHeight());
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 这里没有判断action的类型,是点击还是滑动还是抬起
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// do sth
break;
case MotionEvent.ACTION_MOVE:
// do sth
break;
case MotionEvent.ACTION_UP:
// do sth
break;
default:
break;
}
// 直接设置滑块的y值跟随触摸的y值变化
float y = event.getY();
mTop = (int) y;
requestLayout();
if (y < 0) {
mTop = 0;
}
if (y > getMeasuredHeight() - mChildView.getMeasuredHeight()) {
mTop = getMeasuredHeight() - mChildView.getMeasuredHeight();
}
requestLayout();
return true;
}
public int px2dip(float pxValue) {
final float scale = getContext().getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
// 将do转化为像素尺寸
public int dip2px( float dpValue) {
final float scale = getContext().getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
在xml中使用
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" tools:context="com.mazhan.verticalslideview.MainActivity">
<com.mazhan.verticalslideview.VerticalSlideView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginLeft="20dp"
android:background="#f00">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/tabicon"/>
</com.mazhan.verticalslideview.VerticalSlideView>
</LinearLayout>
9.无限滚动的背景View
通过绘制背景图片来实现
1.创建自定义的属性,来设置图片滚动的参数,创建attrs属性文件,设置参数
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ScrollingView" >
<attr name="speed" format="integer"/>
<attr name="src" format="reference"/>
</declare-styleable>
</resources>
2.在xml中使用自定义的属性
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:mazhan="http://schemas.android.com/apk/res-auto"
tools:context="com.mazhan.scrollingview.MainActivity">
<com.mazhan.scrollingview.ScrollingView
android:layout_width="match_parent"
android:layout_height="wrap_content"
mazhan:src="@drawable/scrolling_background"
mazhan:speed="3"/>
</LinearLayout>
3.创建自定义的ScrollingView,在里面绘制bitmap,通过刷新bitmap的绘制来提现出动画效果
public class ScrollingView extends View {
private int mSpeed;
private Paint mPaint;
private Bitmap mBitmap;
public ScrollingView(Context context) {
super(context);
}
public ScrollingView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
// 获取自定义的属性值
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ScrollingView);
mSpeed = typedArray.getInteger(R.styleable.ScrollingView_speed, 1);
int resourceId = typedArray.getResourceId(R.styleable.ScrollingView_src, 0);
mBitmap = BitmapFactory.decodeResource(getResources(), resourceId);
typedArray.recycle();
mPaint = new Paint();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(widthSize, mBitmap.getHeight());
}
int mOffset = 0;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//1 画图片
//节约效率,避免控件在看不见的地方疯狂绘制,对偏移量需要控制
//负数到了一定的范围,就重置,范围设置为bitmap的宽度
if(mOffset<-mBitmap.getWidth()){
mOffset = 0;
}
//2 填充控件剩下的宽度,多绘制几个bitmap上去
int left = (int) mOffset;
while (left<getMeasuredWidth()){
// 一直往右边去绘制,填充
canvas.drawBitmap(mBitmap,left,0,mPaint);
left+=mBitmap.getWidth();
}
mOffset -= mSpeed;
//4 让控件重新绘制
invalidate();
}
}
网友评论