【移動應用開發(fā)技術(shù)】android中事件分發(fā)機制的實現(xiàn)原理是什么_第1頁
【移動應用開發(fā)技術(shù)】android中事件分發(fā)機制的實現(xiàn)原理是什么_第2頁
【移動應用開發(fā)技術(shù)】android中事件分發(fā)機制的實現(xiàn)原理是什么_第3頁
【移動應用開發(fā)技術(shù)】android中事件分發(fā)機制的實現(xiàn)原理是什么_第4頁
【移動應用開發(fā)技術(shù)】android中事件分發(fā)機制的實現(xiàn)原理是什么_第5頁
已閱讀5頁,還剩15頁未讀, 繼續(xù)免費閱讀

付費下載

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領

文檔簡介

【移動應用開發(fā)技術(shù)】android中事件分發(fā)機制的實現(xiàn)原理是什么

android中事件分發(fā)機制的實現(xiàn)原理是什么,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。android中的事件處理,以及解決滑動沖突問題都離不開事件分發(fā)機制,android中的事件流,即MotionEvent都會經(jīng)歷一個從分發(fā),攔截到處理的一個過程。即dispatchTouchEvent(),onInterceptEvent()到onTouchEvent()的一個過程,在dispatchTouchEvent()負責了事件的分發(fā)過程,在dispatchTouchEvent()中會調(diào)用onInterceptEvent()與onTouchEvent(),如果onInterceptEvent()返回true,那么會調(diào)用到當前view的onTouchEvent()方法,如果不攔截,事件就會下發(fā)到子view的dispatchTouchEvent()中進行同樣的操作。本文將帶領大家從源碼角度來分析android是如何進行事件分發(fā)的。android中的事件分發(fā)流程最先從activity的dispatchTouchEvent()開始:public

boolean

dispatchTouchEvent(MotionEvent

ev)

{

if

(ev.getAction()

==

MotionEvent.ACTION_DOWN)

{

onUserInteraction();

}

if

(getWidow().superDispatchTouchEvent(ev))

{

return

true;

}

return

onTouchEvent(ev);

}這里調(diào)用了getWindow().superDispatchTouchEvent(ev),這里可以看出activity將MotionEvent傳寄給了Window。而Window是一個抽象類,superDispatchTouchEvent()也是一個抽象方法,這里用到的是window的子類phoneWindow。@Override

public

boolean

superDispatchTouchEvent(MotionEvent

event)

{

return

mDecor.superDispatchTouchEvent(event);

}從這里可以看出,event事件被傳到了DecorView,也就是我們的頂層view.我們繼續(xù)跟蹤:public

boolean

superDispatchTouchEvent(MotionEvent

event)

{

return

super.dispatchTouchEvent(event);

}這里調(diào)用到了父類的dispatchTouchEvent()方法,而DecorView是繼承自FrameLayout,F(xiàn)rameLayout繼承了ViewGroup,所以這里會調(diào)用到ViewGroup的dispatchTouchEvent()方法。所以整個事件流從activity開始,傳遞到window,最后再到我們的view(viewGroup也是繼承自view)中,而view才是我們整個事件處理的核心階段。我們來看一下viewGroup的dispatchTouchEvent()中的實現(xiàn):if

(actionMasked

==

MotionEvent.ACTION_DOWN)

{

//

Throw

away

all

previous

state

when

starting

a

new

touch

gesture.

//

The

framework

may

have

dropped

the

up

or

cancel

event

for

the

previous

gesture

//

due

to

an

app

switch,

ANR,

or

some

other

state

change.

cancelAndClearTouchTargets(ev);

resetTouchState();

}這是dispatchTouchEvent()開始時截取的一段代碼,我們來看一下,首先,當我們手指按下view時,會調(diào)用到resetTouchState()方法,在resetTouchState()中:private

void

resetTouchState()

{

clearTouchTargets();

resetCancelNextUpFlag(this);

mGroupFlags

&=

~FLAG_DISALLOW_INTERCEPT;

mNestedScrollAxes

=

SCROLL_AXIS_NONE;

}我們繼續(xù)跟蹤clearTouchTargets()方法:private

void

clearTouchTargets()

{

TouchTarget

target

=

mFirstTouchTarget;

if

(target

!=

null)

{

do

{

TouchTarget

next

=

target.next;

target.recycle();

target

=

next;

}

while

(target

!=

null);

mFirstTouchTarget

=

null;

}

}在clearTouchTargets()方法中,我們最終將mFirstTouchTarget賦值為null,我們繼續(xù)回到dispatchTouchEvent()中,接著執(zhí)行了下段代碼://

Check

for

interception.

final

boolean

intercepted;

if

(actionMasked

==

MotionEvent.ACTION_DOWN

||

mFirstTouchTarget

!=

null)

{

final

boolean

disallowIntercept

=

(mGroupFlags

&

FLAG_DISALLOW_INTERCEPT)

!=

0;

if

(!disallowIntercept)

{

intercepted

=

onInterceptTouchEvent(ev);

ev.setAction(action);

//

restore

action

in

case

it

was

changed

}

else

{

intercepted

=

false;

}

}

else

{

//

There

are

no

touch

targets

and

this

action

is

not

an

initial

down

//

so

this

view

group

continues

to

intercept

touches.

intercepted

=

true;

}當view被按下或mFirstTouchTarget!=null的時候,從前面可以知道,當每次view被按下時,也就是重新開始一次事件流的處理時,mFirstTouchTarget都會被設置成null,一會我們看mFirstTouchTarget是什么時候被賦值的。從disallowIntercept屬性我們大概能猜到是用來判斷是否需要坐攔截處理,而我們知道可以通過調(diào)用父view的requestDisallowInterceptTouchEvent(true)可以讓我們的父view不能對事件進行攔截,我們先來看看requestDisallowInterceptTouchEvent()方法中的實現(xiàn):@Override

public

void

requestDisallowInterceptTouchEvent(boolean

disallowIntercept)

{

if

(disallowIntercept

==

((mGroupFlags

&

FLAG_DISALLOW_INTERCEPT)

!=

0))

{

//

We're

already

in

this

state,

assume

our

ancestors

are

too

return;

}

if

(disallowIntercept)

{

mGroupFlags

|=

FLAG_DISALLOW_INTERCEPT;

}

else

{

mGroupFlags

&=

~FLAG_DISALLOW_INTERCEPT;

}

//

Pass

it

up

to

our

parent

if

(mParent

!=

null)

{

mParent.requestDisallowInterceptTouchEvent(disallowIntercept);

}

}這里也是通過設置標志位做判斷處理,所以這里是通過改變mGroupFlags標志,然后在dispatchTouchEvent()剛發(fā)中變更disallowIntercept的值判斷是否攔截,當為true時,即需要攔截,這個時候便會跳過onInterceptTouchEvent()攔截判斷,并標記為不攔截,即intercepted=false,我們繼續(xù)看viewGroup的onInterceptTouchEvent()處理:public

boolean

onInterceptTouchEvent(MotionEvent

ev)

{

if

(ev.isFromSource(InputDevice.SOURCE_MOUSE)

&&

ev.getAction()

==

MotionEvent.ACTION_DOWN

&&

ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)

&&

isOnScrollbarThumb(ev.getX(),

ev.getY()))

{

return

true;

}

return

false;

}即默認情況下,只有在ACTION_DOWN時,viewGroup才會表現(xiàn)為攔截。我們繼續(xù)往下看:final

int

childrenCount

=

mChildrenCount;

if

(newTouchTarget

==

null

&&

childrenCount

!=

0)

{

final

float

x

=

ev.getX(actionIndex);

final

float

y

=

ev.getY(actionIndex);

//

Find

a

child

that

can

receive

the

event.

//

Scan

children

from

front

to

back.

final

ArrayList<View>

preorderedList

=

buildTouchDispatchChildList();

final

boolean

customOrder

=

preorderedList

==

null

&&

isChildrenDrawingOrderEnabled();

final

View[]

children

=

mChildren;

for

(int

i

=

childrenCount

-

1;

i

>=

0;

i--)

{

final

int

childIndex

=

getAndVerifyPreorderedIndex(

childrenCount,

i,

customOrder);

final

View

child

=

getAndVerifyPreorderedView(

preorderedList,

children,

childIndex);

//

If

there

is

a

view

that

has

accessibility

focus

we

want

it

//

to

get

the

event

first

and

if

not

handled

we

will

perform

a

//

normal

dispatch.

We

may

do

a

double

iteration

but

this

is

//

safer

given

the

timeframe.

if

(childWithAccessibilityFocus

!=

null)

{

if

(childWithAccessibilityFocus

!=

child)

{

continue;

}

childWithAccessibilityFocus

=

null;

i

=

childrenCount

-

1;

}

if

(!canViewReceivePointerEvents(child)

||

!isTransformedTouchPointInView(x,

y,

child,

null))

{

ev.setTargetAccessibilityFocus(false);

continue;

}

newTouchTarget

=

getTouchTarget(child);

if

(newTouchTarget

!=

null)

{

//

Child

is

already

receiving

touch

within

its

bounds.

//

Give

it

the

new

pointer

in

addition

to

the

ones

it

is

handling.

newTouchTarget.pointerIdBits

|=

idBitsToAssign;

break;

}

resetCancelNextUpFlag(child);

if

(dispatchTransformedTouchEvent(ev,

false,

child,

idBitsToAssign))

{

//

Child

wants

to

receive

touch

within

its

bounds.

mLastTouchDownTime

=

ev.getDownTime();

if

(preorderedList

!=

null)

{

//

childIndex

points

into

presorted

list,

find

original

index

for

(int

j

=

0;

j

<

childrenCount;

j++)

{

if

(children[childIndex]

==

mChildren[j])

{

mLastTouchDownIndex

=

j;

break;

}

}

}

else

{

mLastTouchDownIndex

=

childIndex;

}

mLastTouchDownX

=

ev.getX();

mLastTouchDownY

=

ev.getY();

newTouchTarget

=

addTouchTarget(child,

idBitsToAssign);

alreadyDispatchedToNewTouchTarget

=

true;

break;

}

//

The

accessibility

focus

didn't

handle

the

event,

so

clear

//

the

flag

and

do

a

normal

dispatch

to

all

children.

ev.setTargetAccessibilityFocus(false);

}

if

(preorderedList

!=

null)

preorderedList.clear();

}這段代碼首先會通過一個循環(huán)去遍歷所有的子view,最終會調(diào)用到dispatchTransformedTouchEvent()方法,我們繼續(xù)看dispatchTransformedTouchEvent()的實現(xiàn):private

boolean

dispatchTransformedTouchEvent(MotionEvent

event,

boolean

cancel,

View

child,

int

desiredPointerIdBits)

{

final

boolean

handled;

//

Canceling

motions

is

a

special

case.

We

don't

need

to

perform

any

transformations

//

or

filtering.

The

important

part

is

the

action,

not

the

contents.

final

int

oldAction

=

event.getAction();

if

(cancel

||

oldAction

==

MotionEvent.ACTION_CANCEL)

{

event.setAction(MotionEvent.ACTION_CANCEL);

if

(child

==

null)

{

handled

=

super.dispatchTouchEvent(event);

}

else

{

handled

=

child.dispatchTouchEvent(event);

}

event.setAction(oldAction);

return

handled;

}

//

Calculate

the

number

of

pointers

to

deliver.

final

int

oldPointerIdBits

=

event.getPointerIdBits();

final

int

newPointerIdBits

=

oldPointerIdBits

&

desiredPointerIdBits;

//

If

for

some

reason

we

ended

up

in

an

inconsistent

state

where

it

looks

like

we

//

might

produce

a

motion

event

with

no

pointers

in

it,

then

drop

the

event.

if

(newPointerIdBits

==

0)

{

return

false;

}

//

If

the

number

of

pointers

is

the

same

and

we

don't

need

to

perform

any

fancy

//

irreversible

transformations,

then

we

can

reuse

the

motion

event

for

this

//

dispatch

as

long

as

we

are

careful

to

revert

any

changes

we

make.

//

Otherwise

we

need

to

make

a

copy.

final

MotionEvent

transformedEvent;

if

(newPointerIdBits

==

oldPointerIdBits)

{

if

(child

==

null

||

child.hasIdentityMatrix())

{

if

(child

==

null)

{

handled

=

super.dispatchTouchEvent(event);

}

else

{

final

float

offsetX

=

mScrollX

-

child.mLeft;

final

float

offsetY

=

mScrollY

-

child.mTop;

event.offsetLocation(offsetX,

offsetY);

handled

=

child.dispatchTouchEvent(event);

event.offsetLocation(-offsetX,

-offsetY);

}

return

handled;

}

transformedEvent

=

MotionEvent.obtain(event);

}

else

{

transformedEvent

=

event.split(newPointerIdBits);

}

//

Perform

any

necessary

transformations

and

dispatch.

if

(child

==

null)

{

handled

=

super.dispatchTouchEvent(transformedEvent);

}

else

{

final

float

offsetX

=

mScrollX

-

child.mLeft;

final

float

offsetY

=

mScrollY

-

child.mTop;

transformedEvent.offsetLocation(offsetX,

offsetY);

if

(!

child.hasIdentityMatrix())

{

transformedEvent.transform(child.getInverseMatrix());

}

handled

=

child.dispatchTouchEvent(transformedEvent);

}

//

Done.

transformedEvent.recycle();

return

handled;

}這段代碼就比較明顯了,如果child不為null,始終會調(diào)用到child.dispatchTouchEvent();否則調(diào)用super.dispatchTouchEvent();如果child不為null時,事件就會向下傳遞,如果子view處理了事件,即dispatchTransformedTouchEvent()即返回true。繼續(xù)向下執(zhí)行到addTouchTarget()方法,我們繼續(xù)看addTouchTarget()方法的執(zhí)行結(jié)果:private

TouchTarget

addTouchTarget(@NonNull

View

child,

int

pointerIdBits)

{

final

TouchTarget

target

=

TouchTarget.obtain(child,

pointerIdBits);

target.next

=

mFirstTouchTarget;

mFirstTouchTarget

=

target;

return

target;

}這個時候我們發(fā)現(xiàn)mFirstTouchTarget又出現(xiàn)了,這時候會給mFirstTouchTarget重新賦值,即mFirstTouchTarget不為null。也就是說,如果事件被當前view或子view消費了,那么在接下來的ACTION_MOVE或ACTION_UP事件中,mFirstTouchTarget就不為null。但如果我們繼承了該viewGroup,并在onInterceptTouchEvent()的ACTION_MOVE中攔截了事件,那么后續(xù)事件將不會下發(fā),將由該viewGroup直接處理,從下面代碼我們可以得到://

Dispatch

to

touch

targets,

excluding

the

new

touch

target

if

we

already

//

dispatched

to

it.

Cancel

touch

targets

if

necessary.

TouchTarget

predecessor

=

null;

TouchTarget

target

=

mFirstTouchTarget;

while

(target

!=

null)

{

final

TouchTarget

next

=

target.next;

if

(alreadyDispatchedToNewTouchTarget

&&

target

==

newTouchTarget)

{

handled

=

true;

}

else

{

final

boolean

cancelChild

=

resetCancelNextUpFlag(target.child)

||

intercepted;

if

(dispatchTransformedTouchEvent(ev,

cancelChild,

target.child,

target.pointerIdBits))

{

handled

=

true;

}

if

(cancelChild)

{

if

(predecessor

==

null)

{

mFirstTouchTarget

=

next;

}

else

{

predecessor.next

=

next;

}

target.recycle();

target

=

next;

continue;

}

}

predecessor

=

target;

target

=

next;

}當存在子view并且事件被子view消費時,即在ACTION_DOWN階段mFirstTouchTarget會被賦值,即在接下來的ACTION_MOVE事件中,由于intercepted為true,所以將ACTION_CANCEL事件傳遞過去,從dispatchTransformedTouchEvent()中可以看到:if

(cancel

||

oldAction

==

MotionEvent.ACTION_CANCEL)

{

event.setAction(MotionEvent.ACTION_CANCEL);

if

(child

==

null)

{

handled

=

super.dispatchTouchEvent(event);

}

else

{

handled

=

child.dispatchTouchEvent(event);

}

event.setAction(oldAction);

return

handled;

}并將mFirstTouchTarget最終賦值為next,而此時mFirstTouchTarget位于TouchTarget鏈表尾部,所以mFirstTouchTarget會賦值為null,那么接下來的事件將不會進入到onInterceptTouchEvent()中。也就會直接交由該view處理。如果我們沒有進行事件的攔截,而是交由子view去處理,由于ViewGroup的onInterceptTouchEvent()默認并不會攔截除了ACTION_DOWN以外的事件,所以后續(xù)事件將繼續(xù)交由子view去處理,如果存在子view且事件位于子view內(nèi)部區(qū)域的話。所以無論是否進行攔截,事件流都會交由view的dispatchTouchEvent()中進行處理,我們接下來跟蹤一下view中的dispatchTouchEvent()處理過程:if

(actionMasked

==

MotionEvent.ACTION_DOWN)

{

//

Defensive

cleanup

for

new

gesture

stopNestedScroll();

}

if

(onFilterTouchEventForSecurity(event))

{

if

((mViewFlags

&

ENABLED_MASK)

==

ENABLED

&&

handleScrollBarDragging(event))

{

result

=

true;

}

//noinspection

SimplifiableIfStatement

ListenerInfo

li

=

mListenerInfo;

if

(li

!=

null

&&

li.mOnTouchListener

!=

null

&&

(mViewFlags

&

ENABLED_MASK)

==

ENABLED

&&

li.mOnTouchListener.onTouch(this,

event))

{

result

=

true;

}

if

(!result

&&

onTouchEvent(event))

{

result

=

true;

}

}當被按下時,即ACTION_DOWN時,view會停止內(nèi)部的滾動,如果view沒有被覆蓋或遮擋時,首先會進行mListenerInfo是否為空的判斷,我們看下mListenerInfo是在哪里初始化的:ListenerInfo

getListenerInfo()

{

if

(mListenerInfo

!=

null)

{

return

mListenerInfo;

}

mListenerInfo

=

new

ListenerInfo();

return

mListenerInfo;

}這里可以看出,mListenerInfo一般不會是null,知道在我們使用它時調(diào)用過這段代碼,而當view被加入window中的時候,會調(diào)用下面這段代碼,從注釋中也可以看出來:/**

*

Add

a

listener

for

attach

state

changes.

*

*

This

listener

will

be

called

whenever

this

view

is

attached

or

detached

*

from

a

window.

Remove

the

listener

using

*

{@link

#removeOnAttachStateChangeListener(OnAttachStateChangeListener)}.

*

*

@param

listener

Listener

to

attach

*

@see

#removeOnAttachStateChangeListener(OnAttachStateChangeListener)

*/

public

void

addOnAttachStateChangeListener(OnAttachStateChangeListener

listener)

{

ListenerInfo

li

=

getListenerInfo();

if

(li.mOnAttachStateChangeListeners

==

null)

{

li.mOnAttachStateChangeListeners

=

new

CopyOnWriteArrayList<OnAttachStateChangeListener>();

}

li.mOnAttachStateChangeListeners.add(listener);

}到這里我們就知道,mListenerInfo一開始就是被初始化好了的,所以li不可能為null,li.mOnTouchListener!=null即當設置了TouchListener時不為null,并且view是enabled狀態(tài),一般情況view都是enable的。這個時候會調(diào)用到onTouch()事件,當onTouch()返回true時,這個時候result會賦值true。而當result為true時,onTouchEvent()將不會被調(diào)用。從這里可以看出,onTouch()會優(yōu)先onTouchEvent()調(diào)用;當view設置touch監(jiān)聽并返回true時,那么它的onTouchEvent()將被屏蔽。否則會調(diào)用onTouchEvent()處理。那么讓我們繼續(xù)來看看onTouchEvent()中的事件處理:if

((viewFlags

&

ENABLED_MASK)

==

DISABLED)

{

if

(action

==

MotionEvent.ACTION_UP

&&

(mPrivateFlags

&

PFLAG_PRESSED)

!=

0)

{

setPressed(false);

}

//

A

disabled

view

that

is

clickable

still

consumes

the

touch

//

events,

it

just

doesn't

respond

to

them.

return

(((viewFlags

&

CLICKABLE)

==

CLICKABLE

||

(viewFlags

&

LONG_CLICKABLE)

==

LONG_CLICKABLE)

||

(viewFlags

&

CONTEXT_CLICKABLE)

==

CONTEXT_CLICKABLE);

}首先,當view狀態(tài)是DISABLED時,只要view是CLICKABLE或LONG_CLICKABLE或CONTEXT_CLICKABLE,都會返回true,而button默認是CLICKABLE的,textview默認不是CLICKABLE的,而view一般默認都不是LONG_CLICKABLE的。我們繼續(xù)向下看:if

(mTouchDelegate

!=

null)

{

if

(mTouchDelegate.onTouchEvent(event))

{

return

true;

}

}如果有代理事件,仍然會返回true.if

(((viewFlags

&

CLICKABLE)

==

CLICKABLE

||

(viewFlags

&

LONG_CLICKABLE)

==

LONG_CLICKABLE)

||

(viewFlags

&

CONTEXT_CLICKABLE)

==

CONTEXT_CLICKABLE)

{

switch

(action)

{

case

MotionEvent.ACTION_UP:

boolean

prepressed

=

(mPrivateFlags

&

PFLAG_PREPRESSED)

!=

0;

if

((mPrivateFlags

&

PFLAG_PRESSED)

!=

0

||

prepressed)

{

//

take

focus

if

we

don't

have

it

already

and

we

should

in

//

touch

mode.

boolean

focusTaken

=

false;

if

(isFocusable()

&&

isFocusableInTouchMode()

&&

!isFocused())

{

focusTaken

=

requestFocus();

}

if

(prepressed)

{

//

The

button

is

being

released

before

we

actually

//

showed

it

as

pressed.

Make

it

show

the

pressed

//

state

now

(before

scheduling

the

click)

to

ensure

//

the

user

sees

it.

setPressed(true,

x,

y);

}

if

(!mHasPerformedLongPress

&&

!mIgnoreNextUpEvent)

{

//

This

is

a

tap,

so

remove

the

longpress

check

removeLongPressCallback();

//

Only

perform

take

click

actions

if

we

were

in

the

pressed

state

if

(!focusTaken)

{

//

Use

a

Runnable

and

post

this

rather

than

calling

//

performClick

directly.

This

lets

other

visual

state

//

of

the

view

update

before

click

actions

start.

if

(mPerformClick

==

null)

{

mPerformClick

=

new

PerformClick();

}

if

(!post(mPerformClick))

{

performClick();

}

}

}

if

(mUnsetPressedState

==

null)

{

mUnsetPressedState

=

new

UnsetPressedState();

}

if

(prepressed)

{

postDelayed(mUnsetPressedState,

ViewConfiguration.getPressedStateDuration());

}

else

if

(!post(mUnsetPressedState))

{

//

If

the

post

failed,

unpress

right

now

mUnsetPressedState.run();

}

removeTapCallback();

}

mIgnoreNextUpEvent

=

false;

break;

case

MotionEvent.ACTION_DOWN:

mHasPerformedLongPress

=

false;

if

(performButtonActionOnTouchDown(event))

{

break;

}

//

Walk

up

the

hierarchy

to

determine

if

we're

inside

a

scrolling

container.

boolean

isInScrollingContainer

=

isInScrollingContainer();

//

For

views

inside

a

scrolling

container,

delay

the

pressed

feedback

for

//

a

short

period

in

case

this

is

a

scroll.

if

(isInScrollingContainer)

{

mPrivateFlags

|=

PFLAG_PREPRESSED;

if

(mPendingCheckForTap

==

null)

{

mPendingCheckForTap

=

new

CheckForTap();

}

mPendingCheckForTap.x

=

event.getX();

mPendingCheckForTap.y

=

event.getY();

postDelayed(mPendingCheckForTap,

ViewConfiguration.getTapTimeout());

}

else

{

//

Not

inside

a

scrolling

container,

so

show

the

feedback

right

away

setPressed(true,

x,

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論