Android自定义View全解

目录

目录.png

1. 自定义View基础

1.1 分类

自定义View的实现方式有以下几种

类型定义
自定义组合控件多个控件组合成为一个新的控件,方便多处复用
继承系统View控件继承自TextView等系统控件,在系统控件的基础功能上进行扩展
继承View不复用系统控件逻辑,继承View进行功能定义
继承系统ViewGroup继承自LinearLayout等系统控件,在系统控件的基础功能上进行扩展
继承ViewViewGroup不复用系统控件逻辑,继承ViewGroup进行功能定义

1.2 View绘制流程

View的绘制基本由measure()、layout()、draw()这个三个函数完成

函数作用相关方法
measure()测量View的宽高measure(),setMeasuredDimension(),onMeasure()
layout()计算当前View以及子View的位置layout(),onLayout(),setFrame()
draw()视图的绘制工作draw(),onDraw()

1.3 坐标系

在Android坐标系中,以屏幕左上角作为原点,这个原点向右是X轴的正轴,向下是Y轴正轴。如下所示:

 

Android坐标系.png

 

除了Android坐标系,还存在View坐标系,View坐标系内部关系如图所示。

 

视图坐标系.png

View获取自身高度

由上图可算出View的高度:

  • width = getRight() - getLeft();
  • height = getBottom() - getTop();

View的源码当中提供了getWidth()和getHeight()方法用来获取View的宽度和高度,其内部方法和上文所示是相同的,我们可以直接调用来获取View得宽高。

View自身的坐标

通过如下方法可以获取View到其父控件的距离。

  • getTop();获取View到其父布局顶边的距离。
  • getLeft();获取View到其父布局左边的距离。
  • getBottom();获取View到其父布局底边的距离。
  • getRight();获取View到其父布局右边的距离。

1.4 构造函数

无论是我们继承系统View还是直接继承View,都需要对构造函数进行重写,构造函数有多个,至少要重写其中一个才行。如我们新建TestView

public class TestView extends View {/*** 在java代码里new的时候会用到* @param context*/public TestView(Context context) {super(context);}/*** 在xml布局文件中使用时自动调用* @param context*/public TestView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);}/*** 不会自动调用,如果有默认style时,在第二个构造函数中调用* @param context* @param attrs* @param defStyleAttr*/public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}/*** 只有在API版本>21时才会用到* 不会自动调用,如果有默认style时,在第二个构造函数中调用* @param context* @param attrs* @param defStyleAttr* @param defStyleRes*/@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);}
}

1.5 自定义属性

Android系统的控件以android开头的都是系统自带的属性。为了方便配置自定义View的属性,我们也可以自定义属性值。
Android自定义属性可分为以下几步:

  1. 自定义一个View
  2. 编写values/attrs.xml,在其中编写styleable和item等标签元素
  3. 在布局文件中View使用自定义的属性(注意namespace)
  4. 在View的构造方法中通过TypedArray获取

实例说明

  • 自定义属性的声明文件
    <?xml version="1.0" encoding="utf-8"?><resources><declare-styleable name="test"><attr name="text" format="string" /><attr name="testAttr" format="integer" /></declare-styleable></resources>
  • 自定义View类
public class MyTextView extends View {private static final String TAG = MyTextView.class.getSimpleName();//在View的构造方法中通过TypedArray获取public MyTextView(Context context, AttributeSet attrs) {super(context, attrs);TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.test);String text = ta.getString(R.styleable.test_testAttr);int textAttr = ta.getInteger(R.styleable.test_text, -1);Log.e(TAG, "text = " + text + " , textAttr = " + textAttr);ta.recycle();}
}
  • 布局文件中使用
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"xmlns:app="http://schemas.android.com/apk/res/com.example.test"android:layout_width="match_parent"android:layout_height="match_parent" ><com.example.test.MyTextViewandroid:layout_width="100dp"android:layout_height="200dp"app:testAttr="520"app:text="helloworld" /></RelativeLayout>

属性值的类型format

(1). reference:参考某一资源ID

  • 属性定义:
<declare-styleable name = "名称"><attr name = "background" format = "reference" />
</declare-styleable>
  • 属性使用:
<ImageView android:background = "@drawable/图片ID"/>

(2). color:颜色值

  • 属性定义:
<attr name = "textColor" format = "color" />
  • 属性使用:
<TextView android:textColor = "#00FF00" />

(3). boolean:布尔值

  • 属性定义:
<attr name = "focusable" format = "boolean" />
  • 属性使用:
<Button android:focusable = "true"/>

(4). dimension:尺寸值

  • 属性定义:
<attr name = "layout_width" format = "dimension" />
  • 属性使用:
<Button android:layout_width = "42dip"/>

(5). float:浮点值

  • 属性定义:
<attr name = "fromAlpha" format = "float" />
  • 属性使用:
<alpha android:fromAlpha = "1.0"/>

(6). integer:整型值

  • 属性定义:
<attr name = "framesCount" format="integer" />
  • 属性使用:
<animated-rotate android:framesCount = "12"/>

(7). string:字符串

  • 属性定义:
<attr name = "text" format = "string" />
  • 属性使用:
<TextView android:text = "我是文本"/>

(8). fraction:百分数

  • 属性定义:
<attr name = "pivotX" format = "fraction" />
  • 属性使用:
<rotate android:pivotX = "200%"/>

(9). enum:枚举值

  • 属性定义:
<declare-styleable name="名称"><attr name="orientation"><enum name="horizontal" value="0" /><enum name="vertical" value="1" /></attr>
</declare-styleable>
  • 属性使用:
<LinearLayout  android:orientation = "vertical">
</LinearLayout>

注意:枚举类型的属性在使用的过程中只能同时使用其中一个,不能 android:orientation = “horizontal|vertical"

(10). flag:位或运算

  • 属性定义:
<declare-styleable name="名称"><attr name="gravity"><flag name="top" value="0x01" /><flag name="bottom" value="0x02" /><flag name="left" value="0x04" /><flag name="right" value="0x08" /><flag name="center_vertical" value="0x16" />...</attr>
</declare-styleable>
  • 属性使用:
<TextView android:gravity="bottom|left"/>

注意:位运算类型的属性在使用的过程中可以使用多个值

(11). 混合类型:属性定义时可以指定多种类型值

  • 属性定义:
<declare-styleable name = "名称"><attr name = "background" format = "reference|color" />
</declare-styleable>
  • 属性使用:
<ImageView
android:background = "@drawable/图片ID" />
或者:
<ImageView
android:background = "#00FF00" />

2. View绘制流程

这一章节偏向于解释View绘制的源码实现,可以更好地帮助我们掌握整个绘制过程。

View的绘制基本由measure()、layout()、draw()这个三个函数完成

函数作用相关方法
measure()测量View的宽高measure(),setMeasuredDimension(),onMeasure()
layout()计算当前View以及子View的位置layout(),onLayout(),setFrame()
draw()视图的绘制工作draw(),onDraw()

2.1 Measure()

MeasureSpec

MeasureSpec是View的内部类,它封装了一个View的尺寸,在onMeasure()当中会根据这个MeasureSpec的值来确定View的宽高。

MeasureSpec的值保存在一个int值当中。一个int值有32位,前两位表示模式mode后30位表示大小size。即MeasureSpec = mode + size

MeasureSpec当中一共存在三种modeUNSPECIFIEDEXACTLY 和
AT_MOST

对于View来说,MeasureSpec的mode和Size有如下意义

模式意义对应
EXACTLY精准模式,View需要一个精确值,这个值即为MeasureSpec当中的Sizematch_parent
AT_MOST最大模式,View的尺寸有一个最大值,View不可以超过MeasureSpec当中的Size值wrap_content
UNSPECIFIED无限制,View对尺寸没有任何限制,View设置为多大就应当为多大一般系统内部使用

使用方式

    // 获取测量模式(Mode)int specMode = MeasureSpec.getMode(measureSpec)// 获取测量大小(Size)int specSize = MeasureSpec.getSize(measureSpec)// 通过Mode 和 Size 生成新的SpecModeint measureSpec=MeasureSpec.makeMeasureSpec(size, mode);

在View当中,MeasureSpace的测量代码如下:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {int specMode = MeasureSpec.getMode(spec);int specSize = MeasureSpec.getSize(spec);int size = Math.max(0, specSize - padding);int resultSize = 0;int resultMode = 0;switch (specMode) {//当父View要求一个精确值时,为子View赋值case MeasureSpec.EXACTLY://如果子view有自己的尺寸,则使用自己的尺寸if (childDimension >= 0) {resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;//当子View是match_parent,将父View的大小赋值给子View} else if (childDimension == LayoutParams.MATCH_PARENT) {resultSize = size;resultMode = MeasureSpec.EXACTLY;//如果子View是wrap_content,设置子View的最大尺寸为父View} else if (childDimension == LayoutParams.WRAP_CONTENT) {resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;// 父布局给子View了一个最大界限case MeasureSpec.AT_MOST:if (childDimension >= 0) {//如果子view有自己的尺寸,则使用自己的尺寸resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// 父View的尺寸为子View的最大尺寸resultSize = size;resultMode = MeasureSpec.AT_MOST;} else if (childDimension == LayoutParams.WRAP_CONTENT) {//父View的尺寸为子View的最大尺寸resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;// 父布局对子View没有做任何限制case MeasureSpec.UNSPECIFIED:if (childDimension >= 0) {//如果子view有自己的尺寸,则使用自己的尺寸resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {//因父布局没有对子View做出限制,当子View为MATCH_PARENT时则大小为0resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;} else if (childDimension == LayoutParams.WRAP_CONTENT) {//因父布局没有对子View做出限制,当子View为WRAP_CONTENT时则大小为0resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;resultMode = MeasureSpec.UNSPECIFIED;}break;}return MeasureSpec.makeMeasureSpec(resultSize, resultMode);}

这里需要注意,这段代码只是在为子View设置MeasureSpec参数而不是实际的设置子View的大小。子View的最终大小需要在View中具体设置。

从源码可以看出来,子View的测量模式是由自身LayoutParam和父View的MeasureSpec来决定的。

父View mode子View
UNSPECIFIED父布局没有做出限制,子View有自己的尺寸,则使用,如果没有则为0
EXACTLY父布局采用精准模式,有确切的大小,如果有大小则直接使用,如果子View没有大小,子View不得超出父view的大小范围
AT_MOST父布局采用最大模式,存在确切的大小,如果有大小则直接使用,如果子View没有大小,子View不得超出父view的大小范围

在测量子View大小时:

父View mode子View
UNSPECIFIED父布局没有做出限制,子View有自己的尺寸,则使用,如果没有则为0
EXACTLY父布局采用精准模式,有确切的大小,如果有大小则直接使用,如果子View没有大小,子View不得超出父view的大小范围
AT_MOST父布局采用最大模式,存在确切的大小,如果有大小则直接使用,如果子View没有大小,子View不得超出父view的大小范围

onMeasure()

整个测量过程的入口位于Viewmeasure方法当中,该方法做了一些参数的初始化之后调用了onMeasure方法,这里我们主要分析onMeasure

onMeasure方法的源码如下:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}

很简单这里只有一行代码,涉及到了三个方法我们挨个分析。

  • setMeasuredDimension(int measuredWidth, int measuredHeight) :该方法用来设置View的宽高,在我们自定义View时也会经常用到。
  • getDefaultSize(int size, int measureSpec):该方法用来获取View默认的宽高,结合源码来看。
/**
*   有两个参数size和measureSpec
*   1、size表示View的默认大小,它的值是通过`getSuggestedMinimumWidth()方法来获取的,之后我们再分析。
*   2、measureSpec则是我们之前分析的MeasureSpec,里面存储了View的测量值以及测量模式
*/
public static int getDefaultSize(int size, int measureSpec) {int result = size;int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);//从这里我们看出,对于AT_MOST和EXACTLY在View当中的处理是完全相同的。所以在我们自定义View时要对这两种模式做出处理。switch (specMode) {case MeasureSpec.UNSPECIFIED:result = size;break;case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result = specSize;break;}return result;}
  • getSuggestedMinimumWidth():getHeight和该方法原理是一样的,这里只分析这一个。
//当View没有设置背景时,默认大小就是mMinWidth,这个值对应Android:minWidth属性,如果没有设置时默认为0.
//如果有设置背景,则默认大小为mMinWidth和mBackground.getMinimumWidth()当中的较大值。
protected int getSuggestedMinimumWidth() {return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());}

ViewGroup的测量过程与View有一点点区别,其本身是继承自View,它没有对Viewmeasure方法以及onMeasure方法进行重写。

为什么没有重写onMeasure呢?ViewGroup除了要测量自身宽高外还需要测量各个子View的大小,而不同的布局测量方式也都不同(可参考LinearLayout以及FrameLayout),所以没有办法统一设置。因此它提供了测量子View的方法measureChildren()以及measureChild()帮助我们对子View进行测量。

measureChildren()以及measureChild()的源码这里不再分析,大致流程就是遍历所有的子View,然后调用Viewmeasure()方法,让子View测量自身大小。具体测量流程上面也以及介绍过了


measure过程会因为布局的不同或者需求的不同而呈现不同的形式,使用时还是要根据业务场景来具体分析,如果想再深入研究可以看一下LinearLayoutonMeasure方法。

2.2 Layout()

要计算位置首先要对Android坐标系有所了解,前面的内容我们也有介绍过。

layout()过程,对于View来说用来计算View的位置参数,对于ViewGroup来说,除了要测量自身位置,还需要测量子View的位置。

layout()方法是整个Layout()流程的入口,看一下这部分源码

/**
*  这里的四个参数l、t、r、b分别代表View的左、上、右、下四个边界相对于其父View的距离。
*
*/
public void layout(int l, int t, int r, int b) {if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;}int oldL = mLeft;int oldT = mTop;int oldB = mBottom;int oldR = mRight;//这里通过setFrame或setOpticalFrame方法确定View在父容器当中的位置。boolean changed = isLayoutModeOptical(mParent) ?setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);//调用onLayout方法。onLayout方法是一个空实现,不同的布局会有不同的实现。if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {onLayout(changed, l, t, r, b);}}

从源码我们知道,在layout()方法中已经通过setOpticalFrame(l, t, r, b)或 setFrame(l, t, r, b)方法对View自身的位置进行了设置,所以onLayout(changed, l, t, r, b)方法主要是ViewGroup对子View的位置进行计算。

有兴趣的可以看一下LinearLayoutonLayout源码,可以帮助加深理解。

2.3 Draw()

draw流程也就是的View绘制到屏幕上的过程,整个流程的入口在Viewdraw()方法之中,而源码注释也写的很明白,整个过程可以分为6个步骤。

  1. 如果需要,绘制背景。
  2. 有过有必要,保存当前canvas。
  3. 绘制View的内容。
  4. 绘制子View。
  5. 如果有必要,绘制边缘、阴影等效果。
  6. 绘制装饰,如滚动条等等。

通过各个步骤的源码再做分析:

    public void draw(Canvas canvas) {int saveCount;// 1. 如果需要,绘制背景if (!dirtyOpaque) {drawBackground(canvas);}// 2. 有过有必要,保存当前canvas。final int viewFlags = mViewFlags;if (!verticalEdges && !horizontalEdges) {// 3. 绘制View的内容。if (!dirtyOpaque) onDraw(canvas);// 4. 绘制子View。dispatchDraw(canvas);drawAutofilledHighlight(canvas);// Overlay is part of the content and draws beneath Foregroundif (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().dispatchDraw(canvas);}// 6. 绘制装饰,如滚动条等等。onDrawForeground(canvas);// we're done...return;}}/***  1.绘制View背景*/private void drawBackground(Canvas canvas) {//获取背景final Drawable background = mBackground;if (background == null) {return;}setBackgroundBounds();//获取便宜值scrollX和scrollY,如果scrollX和scrollY都不等于0,则会在平移后的canvas上面绘制背景。final int scrollX = mScrollX;final int scrollY = mScrollY;if ((scrollX | scrollY) == 0) {background.draw(canvas);} else {canvas.translate(scrollX, scrollY);background.draw(canvas);canvas.translate(-scrollX, -scrollY);}}/*** 3.绘制View的内容,该方法是一个空的实现,在各个业务当中自行处理。*/protected void onDraw(Canvas canvas) {}/*** 4. 绘制子View。该方法在View当中是一个空的实现,在各个业务当中自行处理。*  在ViewGroup当中对dispatchDraw方法做了实现,主要是遍历子View,并调用子类的draw方法,一般我们不需要自己重写该方法。*/protected void dispatchDraw(Canvas canvas) {}

3. 自定义组合控件

自定义组合控件就是将多个控件组合成为一个新的控件,主要解决多次重复使用同一类型的布局。如我们顶部的HeaderView以及dailog等,我们都可以把他们组合成一个新的控件。

我们通过一个自定义HeaderView实例来了解自定义组合控件的用法。

1. 编写布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:id="@+id/header_root_layout"android:layout_height="45dp"android:background="#827192"><ImageViewandroid:id="@+id/header_left_img"android:layout_width="45dp"android:layout_height="45dp"android:layout_alignParentLeft="true"android:paddingLeft="12dp"android:paddingRight="12dp"android:src="@drawable/back"android:scaleType="fitCenter"/><TextViewandroid:id="@+id/header_center_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:lines="1"android:maxLines="11"android:ellipsize="end"android:text="title"android:textStyle="bold"android:textColor="#ffffff"/><ImageViewandroid:id="@+id/header_right_img"android:layout_width="45dp"android:layout_height="45dp"android:layout_alignParentRight="true"android:src="@drawable/add"android:scaleType="fitCenter"android:paddingRight="12dp"android:paddingLeft="12dp"/></RelativeLayout>

布局很简单,中间是title的文字,左边是返回按钮,右边是一个添加按钮。

2. 实现构造方法

//因为我们的布局采用RelativeLayout,所以这里继承RelativeLayout。
//关于各个构造方法的介绍可以参考前面的内容
public class YFHeaderView extends RelativeLayout {public YFHeaderView(Context context) {super(context);}public YFHeaderView(Context context, AttributeSet attrs) {super(context, attrs);}public YFHeaderView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}}

3. 初始化UI

    //初始化UI,可根据业务需求设置默认值。private void initView(Context context) {LayoutInflater.from(context).inflate(R.layout.view_header, this, true);img_left = (ImageView) findViewById(R.id.header_left_img);img_right = (ImageView) findViewById(R.id.header_right_img);text_center = (TextView) findViewById(R.id.header_center_text);layout_root = (RelativeLayout) findViewById(R.id.header_root_layout);layout_root.setBackgroundColor(Color.BLACK);text_center.setTextColor(Color.WHITE);}

4. 提供对外的方法

可以根据业务需求对外暴露一些方法。

    //设置标题文字的方法private void setTitle(String title) {if (!TextUtils.isEmpty(title)) {text_center.setText(title);}}//对左边按钮设置事件的方法private void setLeftListener(OnClickListener onClickListener) {img_left.setOnClickListener(onClickListener);}//对右边按钮设置事件的方法private void setRightListener(OnClickListener onClickListener) {img_right.setOnClickListener(onClickListener);}

5. 在布局当中引用该控件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><com.example.yf.view.YFHeaderViewandroid:layout_width="match_parent"android:layout_height="45dp"></com.example.yf.view.YFHeaderView></LinearLayout>

到这里基本的功能已经有了。除了这些基础功能外,我们还可以做一些功能扩展,比如可以在布局时设置我的View显示的元素,因为可能有些需求并不需要右边的按钮。这时候就需要用到自定义属性来解决了。

前面已经简单介绍过自定义属性的相关知识,我们之间看代码

1.首先在values目录下创建attrs.xml

内容如下:

<resources><declare-styleable name="HeaderBar"><attr name="title_text_clolor" format="color"></attr><attr name="title_text" format="string"></attr><attr name="show_views"><flag name="left_text" value="0x01" /><flag name="left_img" value="0x02" /><flag name="right_text" value="0x04" /><flag name="right_img" value="0x08" /><flag name="center_text" value="0x10" /><flag name="center_img" value="0x20" /></attr></declare-styleable>
</resources>

这里我们定义了三个属性,文字内容、颜色以及要显示的元素。

2.在java代码中进行设置

    private void initAttrs(Context context, AttributeSet attrs) {TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.HeaderBar);//获取title_text属性String title = mTypedArray.getString(R.styleable.HeaderBar_title_text);if (!TextUtils.isEmpty(title)) {text_center.setText(title);}//获取show_views属性,如果没有设置时默认为0x26showView = mTypedArray.getInt(R.styleable.HeaderBar_show_views, 0x26);text_center.setTextColor(mTypedArray.getColor(R.styleable.HeaderBar_title_text_clolor, Color.WHITE));mTypedArray.recycle();showView(showView);}private void showView(int showView) {//将showView转换为二进制数,根据不同位置上的值设置对应View的显示或者隐藏。Long data = Long.valueOf(Integer.toBinaryString(showView));element = String.format("%06d", data);for (int i = 0; i < element.length(); i++) {if(i == 0) ;if(i == 1) text_center.setVisibility(element.substring(i,i+1).equals("1")? View.VISIBLE:View.GONE);if(i == 2) img_right.setVisibility(element.substring(i,i+1).equals("1")? View.VISIBLE:View.GONE);if(i == 3) ;if(i == 4) img_left.setVisibility(element.substring(i,i+1).equals("1")? View.VISIBLE:View.GONE);if(i == 5) ;}}

3.在布局文件中进行设置

    <com.example.yf.view.YFHeaderViewandroid:layout_width="match_parent"android:layout_height="45dp"app:title_text="标题"app:show_views="center_text|left_img|right_img"></com.example.yf.view.YFHeaderView>

OK,到这里整个View基本定义完成。整个YFHeaderView的代码如下

public class YFHeaderView extends RelativeLayout {private ImageView img_left;private TextView text_center;private ImageView img_right;private RelativeLayout layout_root;private Context context;String element;private int showView;public YFHeaderView(Context context) {super(context);this.context = context;initView(context);}public YFHeaderView(Context context, AttributeSet attrs) {super(context, attrs);this.context = context;initView(context);initAttrs(context, attrs);}public YFHeaderView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);this.context = context;initView(context);initAttrs(context, attrs);}private void initAttrs(Context context, AttributeSet attrs) {TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.HeaderBar);String title = mTypedArray.getString(R.styleable.HeaderBar_title_text);if (!TextUtils.isEmpty(title)) {text_center.setText(title);}showView = mTypedArray.getInt(R.styleable.HeaderBar_show_views, 0x26);text_center.setTextColor(mTypedArray.getColor(R.styleable.HeaderBar_title_text_clolor, Color.WHITE));mTypedArray.recycle();showView(showView);}private void showView(int showView) {Long data = Long.valueOf(Integer.toBinaryString(showView));element = String.format("%06d", data);for (int i = 0; i < element.length(); i++) {if(i == 0) ;if(i == 1) text_center.setVisibility(element.substring(i,i+1).equals("1")? View.VISIBLE:View.GONE);if(i == 2) img_right.setVisibility(element.substring(i,i+1).equals("1")? View.VISIBLE:View.GONE);if(i == 3) ;if(i == 4) img_left.setVisibility(element.substring(i,i+1).equals("1")? View.VISIBLE:View.GONE);if(i == 5) ;}}private void initView(final Context context) {LayoutInflater.from(context).inflate(R.layout.view_header, this, true);img_left = (ImageView) findViewById(R.id.header_left_img);img_right = (ImageView) findViewById(R.id.header_right_img);text_center = (TextView) findViewById(R.id.header_center_text);layout_root = (RelativeLayout) findViewById(R.id.header_root_layout);layout_root.setBackgroundColor(Color.BLACK);text_center.setTextColor(Color.WHITE);img_left.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View view) {Toast.makeText(context, element + "", Toast.LENGTH_SHORT).show();}});}private void setTitle(String title) {if (!TextUtils.isEmpty(title)) {text_center.setText(title);}}private void setLeftListener(OnClickListener onClickListener) {img_left.setOnClickListener(onClickListener);}private void setRightListener(OnClickListener onClickListener) {img_right.setOnClickListener(onClickListener);}}

4. 继承系统控件

继承系统的控件可以分为继承View子类(如TextVIew等)和继承ViewGroup子类(如LinearLayout等),根据业务需求的不同,实现的方式也会有比较大的差异。这里介绍一个比较简单的,继承自View的实现方式。

业务需求:为文字设置背景,并在布局中间添加一条横线。

因为这种实现方式会复用系统的逻辑,大多数情况下我们希望复用系统的onMeaseuronLayout流程,所以我们只需要重写onDraw方法 。实现非常简单,话不多说,直接上代码。

public class LineTextView extends TextView {//定义画笔,用来绘制中心曲线private Paint mPaint;/*** 创建构造方法* @param context*/public LineTextView(Context context) {super(context);init();}public LineTextView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);init();}public LineTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {mPaint = new Paint();mPaint.setColor(Color.BLACK);}//重写draw方法,绘制我们需要的中间线以及背景@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);int width = getWidth();int height = getHeight();mPaint.setColor(Color.BLUE);//绘制方形背景RectF rectF = new RectF(0,0,width,height);canvas.drawRect(rectF,mPaint);mPaint.setColor(Color.BLACK);//绘制中心曲线,起点坐标(0,height/2),终点坐标(width,height/2)canvas.drawLine(0,height/2,width,height/2,mPaint);}
}

对于View的绘制还需要对Paint()canvas以及Path的使用有所了解,不清楚的可以稍微了解一下。

这里的实现比较简单,因为具体实现会与业务环境密切相关,这里只是做一个参考。

5. 直接继承View

直接继承View会比上一种实现方复杂一些,这种方法的使用情景下,完全不需要复用系统控件的逻辑,除了要重写onDraw外还需要对onMeasure方法进行重写。

我们用自定义View来绘制一个正方形。

  • 首先定义构造方法,以及做一些初始化操作
ublic class RectView extends View{//定义画笔private Paint mPaint = new Paint();/*** 实现构造方法* @param context*/public RectView(Context context) {super(context);init();}public RectView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);init();}public RectView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {mPaint.setColor(Color.BLUE);}}
  • 重写draw方法,绘制正方形,注意对padding属性进行设置
/*** 重写draw方法* @param canvas*/@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//获取各个编剧的padding值int paddingLeft = getPaddingLeft();int paddingRight = getPaddingRight();int paddingTop = getPaddingTop();int paddingBottom = getPaddingBottom();//获取绘制的View的宽度int width = getWidth()-paddingLeft-paddingRight;//获取绘制的View的高度int height = getHeight()-paddingTop-paddingBottom;//绘制View,左上角坐标(0+paddingLeft,0+paddingTop),右下角坐标(width+paddingLeft,height+paddingTop)canvas.drawRect(0+paddingLeft,0+paddingTop,width+paddingLeft,height+paddingTop,mPaint);}

之前我们讲到过View的measure过程,再看一下源码对这一步的处理

    public static int getDefaultSize(int size, int measureSpec) {int result = size;int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);switch (specMode) {case MeasureSpec.UNSPECIFIED:result = size;break;case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result = specSize;break;}return result;}

在View的源码当中并没有对AT_MOSTEXACTLY两个模式做出区分,也就是说View在wrap_contentmatch_parent两个模式下是完全相同的,都会是match_parent,显然这与我们平时用的View不同,所以我们要重写onMeasure方法。

  • 重写onMeasure方法
    /*** 重写onMeasure方法** @param widthMeasureSpec* @param heightMeasureSpec*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int widthSize = MeasureSpec.getSize(widthMeasureSpec);int widthMode = MeasureSpec.getMode(widthMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);//处理wrap_contentde情况if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {setMeasuredDimension(300, 300);} else if (widthMode == MeasureSpec.AT_MOST) {setMeasuredDimension(300, heightSize);} else if (heightMode == MeasureSpec.AT_MOST) {setMeasuredDimension(widthSize, 300);}}

整个自定义View的代码如下:

public class RectView extends View {//定义画笔private Paint mPaint = new Paint();/*** 实现构造方法** @param context*/public RectView(Context context) {super(context);init();}public RectView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);init();}public RectView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {mPaint.setColor(Color.BLUE);}/*** 重写onMeasure方法** @param widthMeasureSpec* @param heightMeasureSpec*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int widthSize = MeasureSpec.getSize(widthMeasureSpec);int widthMode = MeasureSpec.getMode(widthMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {setMeasuredDimension(300, 300);} else if (widthMode == MeasureSpec.AT_MOST) {setMeasuredDimension(300, heightSize);} else if (heightMode == MeasureSpec.AT_MOST) {setMeasuredDimension(widthSize, 300);}}/*** 重写draw方法** @param canvas*/@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//获取各个编剧的padding值int paddingLeft = getPaddingLeft();int paddingRight = getPaddingRight();int paddingTop = getPaddingTop();int paddingBottom = getPaddingBottom();//获取绘制的View的宽度int width = getWidth() - paddingLeft - paddingRight;//获取绘制的View的高度int height = getHeight() - paddingTop - paddingBottom;//绘制View,左上角坐标(0+paddingLeft,0+paddingTop),右下角坐标(width+paddingLeft,height+paddingTop)canvas.drawRect(0 + paddingLeft, 0 + paddingTop, width + paddingLeft, height + paddingTop, mPaint);}
}

整个过程大致如下,直接继承View时需要有几点注意:

1、在onDraw当中对padding属性进行处理。
2、在onMeasure过程中对wrap_content属性进行处理。
3、至少要有一个构造方法。

6. 继承ViewGroup

自定义ViewGroup的过程相对复杂一些,因为除了要对自身的大小和位置进行测量之外,还需要对子View的测量参数负责。

需求实例

实现一个类似于Viewpager的可左右滑动的布局。

代码比较多,我们结合注释分析。

public class HorizontaiView extends ViewGroup {private int lastX;private int lastY;private int currentIndex = 0;private int childWidth = 0;private Scroller scroller;private VelocityTracker tracker;/*** 1.创建View类,实现构造函数* 实现构造方法* @param context*/public HorizontaiView(Context context) {super(context);init(context);}public HorizontaiView(Context context, AttributeSet attrs) {super(context, attrs);init(context);}public HorizontaiView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context);}private void init(Context context) {scroller = new Scroller(context);tracker = VelocityTracker.obtain();}/*** 2、根据自定义View的绘制流程,重写`onMeasure`方法,注意对wrap_content的处理* 重写onMeasure方法* @param widthMeasureSpec* @param heightMeasureSpec*/@Overrideprotected 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);//测量所有子ViewmeasureChildren(widthMeasureSpec, heightMeasureSpec);//如果没有子View,则View大小为0,0if (getChildCount() == 0) {setMeasuredDimension(0, 0);} else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {View childOne = getChildAt(0);int childWidth = childOne.getMeasuredWidth();int childHeight = childOne.getMeasuredHeight();//View的宽度=单个子View宽度*子View个数,View的高度=子View高度setMeasuredDimension(getChildCount() * childWidth, childHeight);} else if (widthMode == MeasureSpec.AT_MOST) {View childOne = getChildAt(0);int childWidth = childOne.getMeasuredWidth();//View的宽度=单个子View宽度*子View个数,View的高度=xml当中设置的高度setMeasuredDimension(getChildCount() * childWidth, heightSize);} else if (heightMode == MeasureSpec.AT_MOST) {View childOne = getChildAt(0);int childHeight = childOne.getMeasuredHeight();//View的宽度=xml当中设置的宽度,View的高度=子View高度setMeasuredDimension(widthSize, childHeight);}}/*** 3、接下来重写`onLayout`方法,对各个子View设置位置。* 设置子View位置* @param changed* @param l* @param t* @param r* @param b*/@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {int childCount = getChildCount();int left = 0;View child;for (int i = 0; i < childCount; i++) {child = getChildAt(i);if (child.getVisibility() != View.GONE) {childWidth = child.getMeasuredWidth();child.layout(left, 0, left + childWidth, child.getMeasuredHeight());left += childWidth;}}}
}

到这里我们的View布局就已经基本结束了。但是要实现Viewpager的效果,还需要添加对事件的处理。事件的处理流程之前我们有分析过,在制作自定义View的时候也是会经常用到的,不了解的可以参考之前的文章Android Touch事件分发超详细解析。

    /*** 4、因为我们定义的是ViewGroup,从onInterceptTouchEvent开始。* 重写onInterceptTouchEvent,对横向滑动事件进行拦截* @param event* @return*/@Overridepublic boolean onInterceptTouchEvent(MotionEvent event) {boolean intercrpt = false;//记录当前点击的坐标int x = (int) event.getX();int y = (int) event.getY();switch (event.getAction()) {case MotionEvent.ACTION_MOVE:int deltaX = x - lastX;int delatY = y - lastY;//当X轴移动的绝对值大于Y轴移动的绝对值时,表示用户进行了横向滑动,对事件进行拦截if (Math.abs(deltaX) > Math.abs(delatY)) {intercrpt = true;}break;}lastX = x;lastY = y;//intercrpt = true表示对事件进行拦截return intercrpt;}/*** 5、当ViewGroup拦截下用户的横向滑动事件以后,后续的Touch事件将交付给`onTouchEvent`进行处理。* 重写onTouchEvent方法* @param event* @return*/@Overridepublic boolean onTouchEvent(MotionEvent event) {tracker.addMovement(event);//获取事件坐标(x,y)int x = (int) event.getX();int y = (int) event.getY();switch (event.getAction()) {case MotionEvent.ACTION_MOVE:int deltaX = x - lastX;int delatY = y - lastY;//scrollBy方法将对我们当前View的位置进行偏移scrollBy(-deltaX, 0);break;//当产生ACTION_UP事件时,也就是我们抬起手指case MotionEvent.ACTION_UP://getScrollX()为在X轴方向发生的便宜,childWidth * currentIndex表示当前View在滑动开始之前的X坐标//distance存储的就是此次滑动的距离int distance = getScrollX() - childWidth * currentIndex;//当本次滑动距离>View宽度的1/2时,切换Viewif (Math.abs(distance) > childWidth / 2) {if (distance > 0) {currentIndex++;} else {currentIndex--;}} else {//获取X轴加速度,units为单位,默认为像素,这里为每秒1000个像素点tracker.computeCurrentVelocity(1000);float xV = tracker.getXVelocity();//当X轴加速度>50时,也就是产生了快速滑动,也会切换Viewif (Math.abs(xV) > 50) {if (xV < 0) {currentIndex++;} else {currentIndex--;}}}//对currentIndex做出限制其范围为【0,getChildCount() - 1】currentIndex = currentIndex < 0 ? 0 : currentIndex > getChildCount() - 1 ? getChildCount() - 1 : currentIndex;//滑动到下一个ViewsmoothScrollTo(currentIndex * childWidth, 0);tracker.clear();break;}lastX = x;lastY = y;return true;}private void smoothScrollTo(int destX, int destY) {//startScroll方法将产生一系列偏移量,从(getScrollX(), getScrollY()),destX - getScrollX()和destY - getScrollY()为移动的距离scroller.startScroll(getScrollX(), getScrollY(), destX - getScrollX(), destY - getScrollY(), 1000);//invalidate方法会重绘View,也就是调用View的onDraw方法,而onDraw又会调用computeScroll()方法invalidate();}//重写computeScroll方法@Overridepublic void computeScroll() {super.computeScroll();//当scroller.computeScrollOffset()=true时表示滑动没有结束if (scroller.computeScrollOffset()) {//调用scrollTo方法进行滑动,滑动到scroller当中计算到的滑动位置scrollTo(scroller.getCurrX(), scroller.getCurrY());//没有滑动结束,继续刷新ViewpostInvalidate();}}

这部分代码比较多,为了方便阅读,在代码当中进行了注释。
之后就是在XML代码当中引入自定义View

<com.example.yf.view.HorizontaiViewandroid:id="@+id/test_layout"android:layout_width="match_parent"android:layout_height="400dp"><ListViewandroid:id="@+id/list1"android:layout_width="match_parent"android:layout_height="match_parent"></ListView><ListViewandroid:id="@+id/list2"android:layout_width="match_parent"android:layout_height="match_parent"></ListView><ListViewandroid:id="@+id/list3"android:layout_width="match_parent"android:layout_height="match_parent"></ListView></com.example.yf.view.HorizontaiView>

好了,可以运行看一下效果了。

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 朴素贝叶斯进行文本分类

    这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是…...

    2024/4/19 5:35:39
  2. 1001 害死人不偿命的(3n+1)猜想

    卡拉兹(Callatz)猜想: 对任何一个正整数 n,如果它是偶数,那么把它砍掉一半;如果它是奇数,那么把 (3n+1) 砍掉一半。这样一直反复砍下去,最后一定在某一步得到 n=1。卡拉兹在 1950 年的世界数学家大会上公布了这个猜想,传说当时耶鲁大学师生齐动员,拼命想证明这个貌似很…...

    2024/4/20 5:37:38
  3. myeclipse import导入项目时no projects are found to import解决办法

    原文链接:http://blog.csdn.net/xblackp/article/details/39692575原因:缺少..project和.classpath文件1.在myeclipse中新建一个和你要导入的项目同名的项目.2.在新建的项目中找到.project和.classpath文件,复制。3.将这两个文件粘贴在要导入的项目的根目录下.4.删除第2步创…...

    2024/4/26 11:28:42
  4. ubuntu解决没有nvcc命令的错误

    NVIDIA安装完成后,用nvcc查看安装版本,但是nvcc -V找不到命令。然后在terminal中提示sudo apt-get install nvidia-cuda-toolkit安装解决办法法一:如果存在nvcc可执行文件,但当前用户无法进入cuda的lib等目录,那是权限问题1、进入root用户,在root下配置环境变量,更新配置…...

    2024/4/23 22:01:18
  5. 存图片

    ...

    2024/4/19 13:33:39
  6. Java 8 中 Stream API 的这些奇技淫巧!你都 Get 到了吗?

    Stream简介1、Java 8引入了全新的Stream API。这里的Stream和I/O流不同,它更像具有Iterable的集合类,但行为和集合类又有所不同。2、stream是对集合对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。3、只要给出需要对其包含的元素…...

    2024/3/29 10:26:04
  7. 如何实现报表直接打印需求

    直接打印是常见的报表打印需求,即点击打印按钮后直接把结果输出到打印机,使用户的操作更加简便。润乾报表提供了三种不同的打印方式,都可以实现直接打印功能,下面我们就来看一下每一种方式的具体实现方法。一、直接打印效果1、applet 打印Applet 打印目前只支持 IE 内核浏览…...

    2024/3/29 10:26:01
  8. 上海进口新西兰海鲜需要注意事项及流程

    上海进口新西兰海鲜需要注意事项及流程 2015年新西兰海产品出口创纪录,中国市场占三分之一 据新西兰新闻聚合网站Scoops5月6日报道,2015年新西兰海产品出口创下纪录,内容如下:新西兰海产品协会主执行官Tim Pankhurst宣布,海产品是新西兰的一个主要重要出口收入来源,去年出…...

    2024/4/26 11:17:04
  9. float清除浮动

    清除浮动: 在非IE浏览器下,当容器的高度为auto,且容器的内容中有浮动(float为left或right)的元素,在这种情况下,容器的高度不能自动伸长以适应内容的高度,使得内容溢出到容器外面而影响(甚至破坏)布局的现象。即父级对象盒子无法被撑开,这个现象叫浮动溢出,为了防止…...

    2024/4/20 12:51:27
  10. 排座位(并查集)

    布置宴席最微妙的事情,就是给前来参宴的各位宾客安排座位。无论如何,总不能把两个死对头排到同一张宴会桌旁!这个艰巨任务现在就交给你,对任何一对客人,请编写程序告诉主人他们是否能被安排同席。 输入格式: 输入第一行给出3个正整数:N(≤100),即前来参宴的宾客总人数…...

    2024/4/24 13:29:42
  11. 基础查询

    一.语法 select 查询列表 from 表名 二.特点 1.查询列表可以是字段,常数,表达式,函数,也可以是多个 2.查询结果是一个虚拟表 三.示例 1.查询的单个字段 select 字段名 from 表名 2.查询多个字段 select 字段名,字段名 from 表名 3.查询所有字段 select * from 表名 4.…...

    2024/4/22 21:56:24
  12. Android Service相关认知整理

    这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是…...

    2024/4/25 19:17:22
  13. 离散傅里叶变换

    傅里叶变换将信号分解为正弦波,离散傅里叶变换DFT基于数字信号。real DFT是将输入输出信号都用实数表示,一般用复数DFT,但实数DFT是基础。 傅里叶变换族 傅里叶变换是傅里叶在研究热传导时发现的,他提出用正弦波代表温度分布并向法兰西学会提交论文。但当时的法兰西学会权威…...

    2024/4/26 0:00:39
  14. nuc970 杂记

    1. 系统内存映射表2. 上电前初始化的硬件管脚注意每个管脚上电时需配置对应的电平该功能才能生效!3. CPU主频300MHz与264MHz切换主要修改两个地方,第一个地址0xB0000264修改对应的频率,另个是0xB0001834是对应SDRAM的的延时时间。打印时钟的源码:static void print_clock_i…...

    2024/4/20 15:24:26
  15. Tesseract-OCR 图片数字识别的样本训练

    最近想利用python写一段识别穿越火线交易所各种道具价格的代码。命令行执行:tesseract.exe grab.jpg result -l eng使用默认的Tesseract语言库总会识别成字母或者乱码,如下图:于是参考https://blog.csdn.net/yasi_xi/article/details/8763385这篇帖子,训练了一个对游戏中数…...

    2024/3/29 10:09:37
  16. 时间差 计算

    时间差long nd = 1000 * 24 * 60 * 60;long nh = 1000 * 60 * 60;long nm = 1000 * 60;long time = new Date().getTime()- EpointDateUtil.addDay(workitem.getCreateDate(), 2).getTime();long day = time / nd;long hour = time % nd / nh;long min = time % nd % nh / nm;s…...

    2024/4/26 4:39:16
  17. 在Mac上的视频中添加字幕

    今天分享的教程是如何在Mac上的视频中添加字幕,Mac版Wondershare UniConverter允许您在Mac上的视频中添加字幕。您还可以通过更改字体,颜色,大小,宽度,透明度,位置等来编辑字幕。 步骤1加载视频以在Mac上添加字幕 下载并成功安装后,启动Mac版Wondershare UniConverter。…...

    2024/3/29 10:25:56
  18. 探究 Nginx 中 reload 流程的真相

    今天这篇文章主要来介绍下 Nginx 的 reload 流程。实际上在之前文章中,在更改了 nginx 配置文件时,我们都会执行 nginx -s reload 命令,我们执行这条命令的原因是希望 nginx 不停止服务始终在处理新的请求的同时把 nginx 的配置文件平滑的把旧的 nginx.conf 配置更新为新的 …...

    2024/4/24 22:19:06
  19. pytorch对可变长度序列的处理

    转自:https://www.cnblogs.com/lindaxin/p/8052043.html主要是用函数torch.nn.utils.rnn.PackedSequence()和torch.nn.utils.rnn.pack_padded_sequence()以及torch.nn.utils.rnn.pad_packed_sequence()来进行的,分别来看看这三个函数的用法。1、torch.nn.utils.rnn.PackedSequ…...

    2024/4/7 22:05:16
  20. C++:Z 字形变换

    将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下:L C I R E T O E S I I G E D H N 之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"LCIR…...

    2024/4/25 15:44:29

最新文章

  1. Linux Makefile编写之可执行程序

    1 概述 编译工具有很多(make/cmake/BJam)。如果不考虑跨平台的话&#xff0c;还是make比较方便。使用make编译需要编写Makefile。本文编写Makefile来生成C/C可执行程序。 2 Makefile文件命名 Makefile文件首先是一个文本文件&#xff0c;Linux下默认有两种命名方式: Makefil…...

    2024/4/26 12:52:46
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 2024认证杯数学建模B题思路模型代码

    目录 2024认证杯数学建模B题思路模型代码:4.11开赛后第一时间更新&#xff0c;获取见文末名片 第十三届“认证杯”数学中国数学建模比赛赛后体会 2024认证杯数学建模B题思路模型代码:4.11开赛后第一时间更新&#xff0c;获取见文末名片 第十三届“认证杯”数学中国数学建模比…...

    2024/4/26 10:38:32
  4. 解析大语言模型训练三阶段

    大语言模型的训练过程一般包括3个阶段&#xff1a;预训练&#xff08;Pre-training&#xff09;、SFT&#xff08;有监督的微调&#xff0c;Supervised-Finetuning&#xff09;以及RLHF&#xff08;基于人类反馈的强化学习&#xff0c;Reinforcement Learning from Human Feedb…...

    2024/4/23 6:25:26
  5. DNS和HTTP

    DNS应用层协议 域名解析系统 使用IP地址&#xff0c;来描述设备在网络上的位置 IP地址并不适合来进行传播网站&#xff0c;就采用了域名的方式来解决网站传播的问题。如www.baidu.com这样类似的就很容易让人记住。其域名就直接代表了这个网站。而且有一套自动的系统会将域名解…...

    2024/4/23 6:25:53
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/25 11:51:20
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/25 18:39:24
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/25 18:38:39
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/25 18:39:23
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/25 18:39:22
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/25 18:39:22
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/25 18:39:20
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/25 16:48:44
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/25 13:39:44
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/25 18:39:16
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/25 0:00:17
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/25 4:19:21
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/25 18:39:14
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/25 18:39:12
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/25 2:10:52
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/25 18:39:00
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/25 13:19:01
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/25 18:38:58
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/25 18:38:57
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57