Android App 知识点整理

1:

获取控件宽高

控件View有getHeight()和getwidth()方法可以获取宽高,但是如果直接在onCreate()方法中获取控件宽高,获取到的值是0,至于原因的是因为onCreate()方法中只是提供了数据初始化此时还没有正式绘制图形。而绘制图形在OnDraw中进行,此时计算又显得太晚。容易想到的办法是:希望能在程序刚刚测量好某个指定控件后,拿到它的宽度和高度立刻进行计算或数据初始化。这就需要有一个方法来监听到这个事件的发生,幸好Android提供了这样的机制,利用View类中的getViewTreeObserver方法,可以获取到指定View的观察者,在绘制控件前的一刹那进行回调,这样速度上又不耽误,得到的数据由是准确的。

2:

Animator example:

ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f);

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator animation){

float animValue = (float) animation.getAnimatedValue();

LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mRoot.getLayoutParams();

params.height = (int) (oriHeight + (finalHeight - oriHeight) * animValue);

Log.e(TAG, "params.height = " + params.height);

mRoot.setLayoutParams(params);

mBgImg.setScaleX(1 - animValue);

mBgImg.setScaleY(1 - animValue);

mBgImg.setPivotX(0.5f);

mBgImg.setPivotY(1.0f);

}

});

animator.addListener(new AnimatorListenerAdapter() {

@Override

public void onAnimationStart(Animator animation) {

}

@Override

public void onAnimationEnd(Animator animation) {

mBgImg.setVisibility(View.GONE);

}

});

animator.setDuration(1000);

animator.setInterpolator(new AccelerateInterpolator());

// set target

animator.setTarget(mRoot);

animator.start();

example 2: (?)

Animation outtoLeft = new TranslateAnimation(

Animation.RELATIVE_TO_SELF, 0.0f,

Animation.RELATIVE_TO_SELF, 0.0f,

Animation.RELATIVE_TO_SELF, 0.0f,

Animation.RELATIVE_TO_SELF, 50);

outtoLeft.setDuration(1000);

//        outtoLeft.setFillAfter(true);

//        outtoLeft.setInterpolator(new AccelerateInterpolator());

mRoot.startAnimation(outtoLeft);

example 3: (?)

AnimationmCollapseAnimation=newAnimation()

{

@Override

protected voidapplyTransformation(floatinterpolatedTime,Transformation t) {

inttop =targetTop+ (int)(oriVsOptionHeight* interpolatedTime);

if(interpolatedTime ==1) {

mVsOptionGp.setVisibility(View.GONE);

mCollapseLine.setVisibility(View.VISIBLE);

mRoot.setTop(top);

mRoot.getLayoutParams().height= LayoutParams.WRAP_CONTENT;

mRoot.requestLayout();

}else{

mRoot.setTop(top);

mRoot.requestLayout();

}

@Override

public booleanwillChangeBounds() {

return true;

}

};

mCollapseAnimation.setDuration(500);

mRoot.startAnimation(mCollapseAnimation);

3:

requestLayout:当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent view重新调用他的onMeasure onLayout来对重新设置自己位置。

特别的当view的layoutparameter发生改变,并且它的值还没能应用到view上,这时候适合调用这个方法。

invalidate:View本身调用迫使view重画。

4:

1: 在OnCreate()中获取控件高度与宽度

[java]view plaincopy

ViewTreeObserver observer = view.getViewTreeObserver();

observer .addOnGlobalLayoutListener(newOnGlobalLayoutListener() {

@Override

publicvoidonGlobalLayout() {

view.getViewTreeObserver().removeGlobalOnLayoutListener(this);

finalintw = view.getMeasuredWidth();

finalinth = view.getMeasuredHeight();

}

});

2,启动帧动画

使用ViewTreeObserver.OnPreDrawListener listener:当一个视图树将要绘制时产生事件,可以添加一个其事件处理函数:onPreDraw

[html]view plaincopy

OnPreDrawListeneropdl=newOnPreDrawListener(){

@Override

public boolean onPreDraw() {

animDraw.start();

return true;

}

};

//onCreate方法中

imageV.getViewTreeObserver().addOnPreDrawListener(opdl);

5:

一般可将Android动画分为以下两类的动画系统:

View Animation

View Animation动画系统又可以分类成Tween Animation 和Frame Animation:

Tween Animation

Tween Animation是Android系统比较老的一种动画系统,它的特点是通过对场景里的对象不断做图像变换(渐变、平移、缩放、旋转)产生动画效果,且这种动画只适用于View对象。

Frame Animation

Frame Animation也是常用到的动画,它的原理比较简单,就是将一系列准备好的图片按照顺序播放,形成动画效果。

Property Animation

Property Animation(属性动画)是在Android3.0(API 11)之后引入的一种动画系统,该动画提供了比View Animation更加丰富、强大和灵活的功能,Android官方推荐开发人员使用该动画系统来实现动画。Property Animation的特点是动态地改变对象的属性从而达到动画效果。该动画实现使用于包括View在内的任何对象。

[ref:http://blog.csdn.net/yegongheng/article/details/38366081]

6:

animation.setDuration((int)((targtetHeight - mRoot.getHeight()) / v.getContext().getResources().getDisplayMetrics().density));          // 1dp/ms

7:

shadow drawable: (not perfect)


android:angle="270" />

android:bottom="3dp" />

8:

exoplayer info website:

https://google.github.io/ExoPlayer/

9:

整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为

根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘

(draw),其框架过程如下:

10:

整个View树的结构,对每个具体View对象的操作,其实就是个递归的实现

11:

mesarue()过程 :  为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:

mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。

12:

具体的调用链如下:

ViewRoot根对象地属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调

View/ViewGroup对象的onMeasure()方法,该方法实现的功能如下:

1、设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(对应属性:

mMeasuredHeight)和宽(对应属性:mMeasureWidth)   ;

2 、如果该View对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure()过程。

13:

由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不

会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该

视图需要重绘时,就会为该View添加该标志位。

14:

mView.draw()开始绘制,draw()方法实现的功能如下:

1 、绘制该View的背景

2 、为显示渐变框做一些准备操作(见5,大多数情况下,不需要改渐变框)

3、调用onDraw()方法绘制视图本身   (每个View都需要重载该方法,ViewGroup不需要实现该方法)

4、调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)

值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类

函数实现具体的功能。    [dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法(注意,这个

地方“需要重绘”的视图才会调用draw()方法)]

5、绘制滚动条

15:

将图片裁剪为椭圆形:

@Override

protectedvoidonDraw(Canvas canvas)

{

super.onDraw(canvas);

//将图片裁剪为椭圆形

//构建ShapeDrawable对象并定义形状为椭圆

shapeDrawable =newShapeDrawable(newOvalShape());

//得到画笔并设置渲染器

shapeDrawable.getPaint().setShader(bitmapShader);

//设置显示区域

shapeDrawable.setBounds(20,20,BitmapWidth-60,BitmapHeight-60);

//绘制shapeDrawable

shapeDrawable.draw(canvas);

}

16:

颜色选择器:

publicclassShaderViewextendsView {

privateShader mSweepGradient =null;

privatePaint mPaint;

publicShaderView(Context context, AttributeSet attrs) {

super(context, attrs);

init();

}

privatevoidinit() {

mPaint =newPaint(Paint.ANTI_ALIAS_FLAG);

mSweepGradient =newSweepGradient(400,400,newint[] { Color.YELLOW,

Color.RED, Color.BLUE, Color.GREEN },newfloat[] {0, .2F,

.6F, .9F });

}

@Override

protectedvoidonDraw(Canvas canvas) {

super.onDraw(canvas);

mPaint.setShader(mSweepGradient);

canvas.drawCircle(400,400,300, mPaint);

}

}

17:

Canvas中的各种drawXXX方法定义了图形的形状,画笔中的Shader则定义了图形的着色、外观,二者结合到一起就决定了最终Canvas绘制的被色彩填充的图形的样子

18:

渐变效果:

LinearGradient linearGradient = new LinearGradient(100, 100, 500, 500, Color.GREEN, Color.BLUE, Shader.TileMode.CLAMP);

paint.setShader(linearGradient);

canvas.drawRect(100, 100, 500, 500, paint);

19:

火花粒子滑动效果:

https://github.com/a396901990/SparkScreen

20:

粒子效果:https://github.com/JeasonWong/Particle

21:  TreeMap排序例子:

MapmSortedOptionList;

private voidsortOptionByCount(List list) {

HashMap map =newHashMap<>();

for(inti =0;i < list.size();i++) {

map.put(list.get(i).count,mOptionViewList.get(i));

}

mSortedOptionList=newTreeMap(map);

mOptionRanked.set(true);

}

22:  Advanced blurring techniques:http://trickyandroid.com/advanced-blurring-techniques/.

23: onDraw实现自定义view

24: 替换服务器: request url update - >BFJSONRequest.

25:How to make an ImageView to have rounded corners[http://stackoverflow.com/questions/14473113/bitmap-image-with-rounded-corners-with-stroke]

1:

publicstaticBitmapgetRoundedCornerBitmap(Bitmapbitmap,intpixels) {Bitmapoutput =Bitmap.createBitmap(bitmap.getWidth(), bitmap

.getHeight(),Config.ARGB_8888);Canvascanvas =newCanvas(output);finalintcolor =0xff424242;finalPaintpaint =newPaint();finalRectrect =newRect(0,0, bitmap.getWidth(), bitmap.getHeight());finalRectFrectF =newRectF(rect);finalfloatroundPx = pixels;

paint.setAntiAlias(true);

canvas.drawARGB(0,0,0,0);

paint.setColor(color);

canvas.drawRoundRect(rectF, roundPx, roundPx, paint);

paint.setXfermode(newPorterDuffXfermode(Mode.SRC_IN));

canvas.drawBitmap(bitmap, rect, rect, paint);returnoutput;

}

26:

setMeasuredDimension :  设置view的大小. [照片相框?]

measureChild(getChildAt(0), ...) :  测量第一个子view的大小, 一般用于设置attach的xml layout的大小 [ 照片相框?], 会被父layout 覆盖限制.

27:

获取动态内容view的高度:  可尝试onMeasure中.

if(mOptionGp.getVisibility() == View.VISIBLE) {

if(!mBAnimating) {

mOptionViewHeight=mOptionGp.getMeasuredHeight();

}

}

28: View的大小确认图:

29:

setMeasuredDimension: 设置view的展示相框大小

measureChild(getChildAt(0), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), ...) :  不受约束测量子view大小

30:

CenteredImageSpan例子:

public classCenteredImageSpanextendsImageSpan {

// Extra variables used to redefine the Font Metrics when an ImageSpan is added

private intinitialDescent=0;

private intextraSpace=0;

publicCenteredImageSpan(finalDrawable drawable) {

this(drawable,DynamicDrawableSpan.ALIGN_BASELINE);

}

publicCenteredImageSpan(finalDrawable drawable, final intverticalAlignment) {

super(drawable,verticalAlignment);

}

// Method used to redefined the Font Metrics when an ImageSpan is added

@Override

public intgetSize(Paint paint,CharSequence text,

intstart, intend,

Paint.FontMetricsInt fm) {

Drawable d = getCachedDrawable();

Rect rect = d.getBounds();

if(fm !=null) {

// Centers the text with the ImageSpan

if(rect.bottom- (fm.descent- fm.ascent) >=0) {

// Stores the initial descent and computes the margin available

initialDescent= fm.descent;

extraSpace= rect.bottom- (fm.descent- fm.ascent);

}

fm.descent=extraSpace/2+initialDescent;

fm.bottom= fm.descent;

fm.ascent= -rect.bottom+ fm.descent;

fm.top= fm.ascent;

}

returnrect.right;

}

// Redefined locally because it is a private member from DynamicDrawableSpan

privateDrawablegetCachedDrawable() {

WeakReference wr =mDrawableRef;

Drawable d =null;

if(wr !=null)

d = wr.get();

if(d ==null) {

d = getDrawable();

mDrawableRef=newWeakReference<>(d);

}

returnd;

}

privateWeakReferencemDrawableRef;

}

31: shadow阴影style:

4px

4px

#000000

12sp

@color/white

3

3

3

32:

测量view的大小(目前可不管是否visible or gone):

mOptionGp.measure(MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED),MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED));

mOptionGpWidth=mOptionGp.getMeasuredWidth();

33:

图片大小 电脑实际像素x :  x  / 2 =  x dip.

34:

HorizontalPagerAdapter为首页背景视频

35:

MultipleCameraView:  所有视频的容器

CameraVIew:  单个视频View

VRCarmerView, VRMixedCarmerView subclass of cameraview

AbstractVideoHelper :  播放器功能类

36:

Html实现图文混排:

String html ="

";

Html.ImageGetter imgGetter =newHtml.ImageGetter() {

@Override

publicDrawablegetDrawable(String source) {

//TODO Auto-generated method stub

intid = Integer.parseInt(source);

Drawable d = getResources().getDrawable(id);

d.setBounds(0,0,d.getIntrinsicWidth() +10,d.getIntrinsicHeight() +10);

returnd;

}

};

CharSequence charSequence = Html.fromHtml(html,imgGetter, null);

mTitleText.setText(charSequence);

mTitleText.append("  Html实现图文混排");

37:

去除混淆:proguard-rules.pro:-dontobfuscate

38:

ViewPager Transformer动画:

https://github.com/ToxicBakery/ViewPagerTransforms/blob/master/library/src/main/java/com/ToxicBakery/viewpager/transforms/ABaseTransformer.java

39:

GoodsCardController的manageCard一直刷新

PluginLoader. getHomeModules 获取模块信息, com.aube.app.module.android.list

loadPluginFile获取服务器模块信息开始处理

40:

GridVideoGroup

41:

定制ViewPager翻滚动画

public voidsetScrollerAnimation(ABaseTransformer animation){

mViewPager.setPageTransformer(true,animation);

}

setScrollerAnimation(newABaseTransformer() {

@Override

protected voidonTransform(View page, floatposition) {

float translationX = position < 0 ? 0f : -page.getWidth() * position;

page.setTranslationX(translationX);

}

});

42:

DLProxyContext为获取host的context给plugin用. ( PlayerModuleManager的load()).

43:

HOME_DETAIL接口中, Actor和首页信息区别: 1)relatedId    2)templateCode

44:

开源资料:

https://github.com/Tencent.

https://github.com/TencentOpen

http://www.open-open.com/lib/view/open1377700292339.html

http://alloyteam.github.io/

45:

PlayerController.onDestroy. 播放器退出

StaticApplicationContext.receivedDataSize 流量统计

AubeStatisticTool

多镜头入场逻辑: MultiScreenController.animIn()

CameraCardView: 镜头的View.  switchWindowSize切换大小

MultiCameraTransitionHelper: 多镜头转换处理逻辑?

视频View:  VRCardView, VRMixedCardView, MultiCameraCardView, CameraCardView.

46:

IPlayPresenter为插件和Host之间的纽带.

PlayerProxy只是播放器UI层面的代理.

47:

APP的入口:

LaunchActivity. LaunchApplication.

LaunchActivity. 继承于StartActivity. 从project(taste)转到app. StartActivity开启空跳转至PluginMainActivity.

PluginMainActivity为主页的内容容器Activity.......

LaunchApplication现在弃用,用LaunchApplicationLike, 参与到tinker的应用生命周期管理,便于补丁升级.

48:

Github: master password: &gh

49:

Create or Import a Native Project:https://developer.android.com/ndk/guides/index.html#native-project

50:

Google例子代码:https://github.com/googlesamples

Ndk例子代码:https://github.com/googlesamples/android-ndk

Google的arch例子:https://github.com/googlesamples/android-architecture.

lua:https://github.com/henkel/lua-android/tree/master/project/jni/lua-5.1.4.

51:

externalNativeBuild {

ndkBuild {

path 'src/main/jni/Android.mk'

}

}

52: exoplayer 播放代码:

private voidpreparePlayer(booleanplayWhenReady) {

if(player==null) {

player=newDemoPlayer(getRendererBuilder());

player.addListener(this);

player.seekTo(playerPosition);//播放进度的设置

playerNeedsPrepare=true;//是否立即播放

}

if(playerNeedsPrepare) {

player.prepare();

playerNeedsPrepare=false;

}

player.setSurface(surfaceView.getHolder().getSurface());

player.setPlayWhenReady(playWhenReady);

}

53:

JNI的具体开发流程总结起来分为这么几步:

(1)在原生java类中声明native方法。native表明该方法为一个本地方法,由C/C++实现。

(2)使用javac命令将带有声明native方法的类,编译成class字节码。javac是jdk自带的一个命令,一般在javapath/bin(javapath为java安装目录)路径下。

(3)使用javah命令将编译好的class生成本地C/C++代码的.h头文件。同样,javah也是jdk自带的一个命令。

(4)实现.h头文件中的方法。

(5)将本地代码编译成动态库。注意,不同平台的动态库是不一样的。

(6)在java工程中引用编译好的动态库。

54:

void remove_blanks (char *s)

{

lua_pushstring(s);  /* prepare parameter */

lua_call("remove_blanks");  /* call Lua function */

strcpy(s, lua_getstring(lua_getresult(1)));  /* copy result back to 's' */

}

55:

@Override

protected void onDraw(Canvas canvas) {

Paint paint = mPaint; canvas.drawColor(Color.WHITE); if(MotionEvent.ACTION_MOVE == mAction) {

paint.setColor(Color.RED);

}else if(MotionEvent.ACTION_UP == mAction) {

//移动动作

//抬起动作

paint.setColor(Color.GREEN);

}else if(MotionEvent.ACTION_DOWN == mAction) { //按下动作

paint.setColor(Color.BLUE);

}

canvas.drawCircle(mX, mY,10, paint);

setTitle("A = " + mAction + " ["+ mX +","+ mY +"]"); }

@Override

public boolean onTouchEvent(MotionEvent event) {

mAction = event.getAction(); //获得动作mX = event.getX(); //获得坐标mY = event.getY();

Log.v(TAG, "Action = "+ mAction ); Log.v(TAG, "("+mX+","+mY+")"); invalidate();

return true;

}

56:

带有返回值的跳转:

source:

startActivityForResult (intent, GET_CODE);

@Override

protected void onActivityResult(int requestCode, int resultCode,

Intent data) {

if (requestCode == GET_CODE) {

Editable text = (Editable)mResults.getText(); if (resultCode == RESULT_CANCELED) {

text.append("(cancelled)");

} else {

text.append("(okay ");

text.append(Integer.toString(resultCode));

text.append(") ");

if (data != null) {

text.append(data.getAction());

}

}

text.append("\n");

}

}

target:

private OnClickListener mCorkyListener = new OnClickListener() {

public void onClick(View v)

{

setResult(RESULT_OK, (new Intent()).setAction("Corky!"));

finish(); }

};

private OnClickListener mVioletListener = new OnClickListener() {

public void onClick(View v)

{

setResult(RESULT_OK, (new Intent()).setAction("Violet!")); finish();

} };

57:

菜单:

public boolean onCreateOptionsMenu(Menu menu)

public boolean onOptionsItemSelected(MenuItem item)

onCreateOptionsMenu()用于在建立菜单时进行设置,建立时为每一个按钮设置ID,菜单项被选择时调用onOptionsItemSelected(),通过MenuItem类的getItemId()函数获得这个菜单的ID,继续进行处理。

菜单类在Android中表示为android.view.Menu类。使用这个类可以进行一些更 为细节的设置和操作。

abstract MenuItem add(int groupId, int itemId, int order, CharSequence title) abstract MenuItem add(int groupId, int itemId, int order, int titleRes)

add()的第1、2个参数是整数值,分别代表按钮项的组ID和选项ID,第3个参 数用于设置按钮上的文件。

58:

在Android程序中,当某一个活动启动之后可能需要使用背景透明的效果.

AndroidManifest.xml中的定义如下所示:

这两个程序使用的都是窗口透明的效果,TranslucentActivity获得的效果是背 景普通的透明,TranslucentBlurActivity获得的效果是背景模糊的透明。它们的样 式被设置成了Translucent,这是一个用于描述背景透明的自定义样式,在styles.xml中定义。


@drawable/translucent_background

true

#fff

TranslucentBlurActivity之所以能够获得背景模糊的效果,是因为在源代码中 进行了进一步的设置.

public class TranslucentBlurActivity extends Activity {

protected void onCreate(Bundle icicle) {

super.onCreate(icicle);

getWindow(). setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,

WindowManager.LayoutParams.FLAG_BLUR_BEHIND); setContentView(R.layout.translucent_background);

} }

设置模糊效果是通过窗口管理器(WindowManager)设置参数来完成的,这 种设置只有在背景设置为透明后才能显示效果。

59:

60:

文本切换器(TextSwitcher)是Android中一个集成化较高的控件,可以在多 个文本之间切换,还可以设置动画的效果。

图像切换器(ImageSwitcher)和文本切换器类似,但是显示的内容是多个图片中 的一个。

61:

在GUI系统中,图形API是比较底层的接口。Android系统的图形API包括2D和3D两部分:2D部分使用android.graphics类,也作为上层控件的构建基础;3D部分使用OpenGL作为标准接口。

62:

在使用2D的图形API方面,步骤通常如下所示:

1、扩展实现android.view.View类。

2、实现View的OnDraw()函数,在其中使用Canvas的方法进行绘制。 使用2D的图形API的场合,自定义实现的View类型作为下层的绘制和上层的GUI系统中间层。

android.graphics.drawable包是Android中一个绘制相关的包,表示一些可以

被绘制的东西。在Android中Drawable的􏰁义就是可以仅仅是为了显示来使用的, 与View的主要区别就在于Drawable不能从用户处获得事件的反馈。

63:

private static class SampleView extends View { private Bitmap mBitmap;

private Bitmap mBitmap2;

private Bitmap mBitmap3;

private Shader mShader;

public SampleView(Context context) {

super(context);

setFocusable(true);

InputStream is = context.getResources().

openRawResource(R.drawable.app_sample_code); mBitmap = BitmapFactory.decodeStream(is); //解码位图文件到Bitmap

mBitmap2 = mBitmap.extractAlpha(); //提取位图的透明通道

//创建一个位图

mBitmap3 = Bitmap.createBitmap(200, 200, Bitmap.Config.ALPHA_8); drawIntoBitmap(mBitmap3); //调 用 自 己 实 现 的

drawIntoBitmap()

mShader = new LinearGradient(0, 0, 100, 70, new int[] {

Color.RED, Color.GREEN, Color.BLUE }, null, Shader.TileMode.MIRROR);

}

private static void drawIntoBitmap(Bitmap bm) {

float x = bm.getWidth();

float y = bm.getHeight();

Canvas c = new Canvas(bm);

Paint p = new Paint();

p.setAntiAlias(true);

p.setAlpha(0x80);

c.drawCircle(x/2, y/2, x/2, p);

p.setAlpha(0x30);

p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); p.setTextSize(60);

p.setTextAlign(Paint.Align.CENTER);

Paint.FontMetrics fm = p.getFontMetrics(); c.drawText("Alpha", x/2, (y-fm.ascent)/2, p);

}

@Override protected void onDraw(Canvas canvas) {

笔)

} }

canvas.drawColor(Color.WHITE);

Paint p = new Paint();

float y = 10;

p.setColor(Color.RED); canvas.drawBitmap(mBitmap, 10, y, p); y += mBitmap.getHeight() + 10; canvas.drawBitmap(mBitmap2, 10, y, p);

y += mBitmap2.getHeight() + 10; p.setShader(mShader); canvas.drawBitmap(mBitmap3, 10, y, p);

第1个图是直接对原始的图像进行了绘制;第2个图是在原始图像的基础上 抽取了透明通道,所以绘制时画笔(Paint)的颜色起到了作用;第3个图是调用drawIntoBitmap()绘制了一个具有渐变颜色的圆,并附加了文字。

64:

对齐操作不仅有水平和竖直上的对齐问题,甚至可以让文本在 曲线的路径上实现对齐:

文本的对其操作主要通过以下两点来完成:

1.通过画笔(Paint)的setTextAlign()函数设置绘制过程中的对齐方式。2.drawText(),drawPosText(),drawTextOnPath()几个函数表示了文本的几

种绘制方式。drawText()在指定的坐标上进行文本绘制;drawPosText()在一个表 示为位置信息的数组上进行文本绘制(其中的float[] pos参数表示交替的x和y表示的坐标);drawTextOnPath()表示在一个路径(Path)进行文本绘制。

65:

在使用3D的图形API方面,主要的步骤通常如下所示:

1.扩展实现android.view.GLSurfaceView类。

2.扩展实现android.opengl.GLSurfaceView中的Renderer(渲染器)。3.实现GLSurfaceView::Renderer中的onDrawFrame()等函数。

66:

OpenGL本身带回动画绘制的功能,这里使用的glRotatef()是进行旋转。mTriangle是一个三角形,在onDrawFrame()根据OpenGL的上下文进行动画的绘 制。

67:

class a implemens OnKeyLisnter {

@Override

public void onKey(View v,  KeyCode keycode, KeyEvent event ) {

switch(event.getAction() {

String msg = Class.this.getText().toString();

if(msg.matches("\\w+@\\w+\\.\\w+")) {        //判断是否是email地址.

68:

绘图View中,  onTouch中, onDown创建搜集点列表,并添加第一个点, 在onUp和onMove时候添加点并postInvalidate;

在onDraw中, 每个点canvas.DrawLine.

69:

ListActivity的使用:

70:

AutoCompleteTextView自动补完textview.

隐时抽屉组件: SlidingDrawer.

树型组件:ExpandableListView

71:

自定义Seekbar的view:

@Override

protectedvoidonDraw(Canvas canvas){

intwidth=getWidth();

intheight=getHeight();

intlineHeight=height/4;

/*画背景条*/

mPaint.setColor(mNormalColor);

RectF rectF=newRectF(0,(height-lineHeight)/2,width,(height+lineHeight)/2);

canvas.drawRoundRect(rectF,lineHeight/2,lineHeight/2,mPaint);

/*画缓冲条*/

mPaint.setColor(mBufferColor);

if(mBuffer>=99){

canvas.drawRoundRect(rectF,lineHeight/2,lineHeight/2,mPaint);

}else{

canvas.drawRect(newRect(lineHeight/2,(height-lineHeight)/2,width*mBuffer/100,(height+lineHeight)/2),mPaint);

}

/*画已播放条 */

mPaint.setColor(mPlayedColor);

canvas.drawRoundRect(newRectF(0,(height-lineHeight)/2,width*mProgress/100,(height+lineHeight)/2),lineHeight/2,lineHeight/2,mPaint);

if(mEnabled){

/*画highlight点*/

if(mHighlights!=null&&mVideoLength>0){

for(Integer i:mHighlights){

intpercent=(int)(i.intValue()*100/mVideoLength);

intpoint=(int)(width*i.intValue()/mVideoLength);

mPaint.setColor(percent>mProgress?mHighlightColorGray:mHighlightColorYellow);

canvas.drawOval(newRectF(point-lineHeight/2,(height-lineHeight)/2,point+lineHeight/2,(height+lineHeight)/2),mPaint);

}

}

}

/*画圆点*/

intprogress=width*mProgress/100;

intstartX=progress-height/2;

intendX=progress+height/2;

if(startX<0){

startX=0;

endX=height;

}

if(endX>width){

endX=width;

startX=width-height;

}

mPaint.setColor(mPlayedColor);

canvas.drawOval(newRectF(startX,0,endX,height),mPaint);

}

72:

View的interface , interface 1个, 自己带接口3个(包括供给外部用的),  外部一个.

73:使用SharedPreference保存数据:

SharedPreferences share = super.getSharedPreferences(FILENAME, Activity.MODE_PRIVATE);

SharedPreferences.Edit edit = share.edit();

edit.putString("author", "Liss");

edit.putInt("age", 30);

edit.commit();

74:

创建文件IO流:

File file =new File (Environment.getExternalStorageDirectory.toString() + File.Separator + DIRNAME + File.Separator + FILENAME);

读取文件流:

this.msg = new StrigBuffer();

try {

scan = new Scanner(new FileInputStream(file));

while(scan.hasNext()) {

this.msg.append(scan.next() + "\n";

} CatchException (Exception e) {

} finally {

if(scan != null) {

scan.close();

}

读取资源文件信息:

InputStream input = getResources().openRawResources(R.raw.book);

75: DOM文件信息操作:

写入:

读取:

76:

JSON更为方便.

ContentProvider应用程序间使用数据,有点像web url.

77:

standard: 标准的,会重复创建栈顶活动

SignleTop:不会重复创建已经在栈顶的活动.

SingleTask: 解决重复创建栈顶活动的问题,如果已经有,则出栈之前所有的活动

SingleInstance启动新的返回栈来管理活动,用于应用程序间共享活动实例

78:

随时随地退出程序:

public class ActivityCollector {

public static List activities = new ArrayList();

public static void addActivity(Activity activity) { activities.add(activity);

}

public static void removeActivity(Activity activity) { activities.remove(activity);

}

public static void finishAll() {

for (Activity activity : activities) {

if (!activity.isFinishing()) { activity.finish();

} }

}

}

使用:

@Override

protected void onDestroy() {

super.onDestroy();

ActivityCollector.removeActivity(this); }

从此以后,不管你想在什么地方退出程序,只需要调用 ActivityCollector.finishAll() 方法就可以了。例如在 ThirdActivity 界面想通过点击按钮直接退出程序,只需将代码改 成如下所示:

...

@Override

public void onClick(View v) {

ActivityCollector.finishAll(); }

...

79:

每次在 getView()方法中还是会调用 View 的 findViewById()方法来获取一次控件的实 例。我们可以借助一个 ViewHolder 来对这部分性能进行优化:

public class FruitAdapter extends ArrayAdapter { ⋯⋯

@Override

public View getView(int position, View convertView, ViewGroup parent) {

Fruit fruit = getItem(position); View view;

ViewHolder viewHolder;

if (convertView == null) {

view = LayoutInflater.from(getContext()).inflate(resourceId, null);

viewHolder = new ViewHolder();

viewHolder.fruitImage = (ImageView) view.findViewById (R.id.fruit_image); viewHolder.fruitName = (TextView) view.findViewById (R.id.fruit_name); view.setTag(viewHolder); // 将ViewHolder存储在View中

} else {

view = convertView;

viewHolder = (ViewHolder) view.getTag(); // 重新获取ViewHolder

} viewHolder.fruitImage.setImageResource(fruit.getImageId()); viewHolder.fruitName.setText(fruit.getName());

return view;

}

class ViewHolder {

ImageView fruitImage;

TextView fruitName;

}

}

80:

动态添加Fragment:

AnotherRightFragment fragment = new AnotherRightFragment();

FragmentManager fragmentManager = getFragmentManager();

FragmentTransaction transaction = fragmentManager. beginTransaction();

transaction.replace(R.id.right_layout, fragment);

transaction.addToBackStack(null); ///在Fragment中模拟返回栈, 按返回退出Fragment

transaction.commit();

创建待添加的碎片实例。

获取到 FragmentManager,在活动中可以直接调用 getFragmentManager()

方法得到。

开启一个事务,通过调用 beginTransaction()方法开启。

向容器内加入Fragment,一般使用 replace()方法实现,需要传入容器的 id 和待添加的

碎片实例。

提交事务,调用 commit()方法来完成

81:

Fragment与Activity通信:

FragmentManager 提供了一个类似于 findViewById()的方法,专门用于从布局文件中获取碎片的实例,代码如下所示:

RightFragment rightFragment = (RightFragment) getFragmentManager() .findFragmentById(R.id.right_fragment);

调用 FragmentManager 的 findFragmentById()方法,可以在活动中得到相应碎片 的实例,然后就能轻松地调用碎片里的方法了。

相反, Fragment直接通过getActivity获取activity实例.

82:

Fragment同样拥有activity的回调方法, 还有自己独特需求的方法:

onAttach() 当碎片和活动建立关联的时候调用。

onCreateView() 为碎片创建视图(加载布局)时调用。

onActivityCreated() 确保与碎片相关联的活动一定已经创建完毕的时候调用。

onDestroyView() 当与碎片关联的视图被移除的时候调用。

onDetach() 当碎片和活动解除关联的时候调用。

83:

localBroadcastReceiver用于在应用程序内通信, 它不接受外部应用程序的广播,也不发送给外部应用程序.

84:

SQLite存储数据:

Android 为了让我们能够更加方便地管理数据库,专门提供了一个 SQLiteOpenHelper 帮助类,借助这个类就可以非常简单地对数据库进行创建和升级.

85:

public class MyDatabaseHelper extends SQLiteOpenHelper {

public static final String CREATE_BOOK = "create table book (" + "id integer primary key autoincrement, "

+ "author text, "

+ "price real, "

+ "pages integer, " + "name text)";

private Context mContext;

public MyDatabaseHelper(Context context, String name, CursorFactory factory, int version) {

super(context, name, factory, version);

mContext = context; }

@Override

public void onCreate(SQLiteDatabase db) {

db.execSQL(CREATE_BOOK);

Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show(); }

@Override

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { }

}

可以看到,我们把建表语句定义成了一个字符串常量,然后在 onCreate()方法中又调 用了 SQLiteDatabase 的 execSQL()方法去执行这条建表语句,并弹出一个 Toast 提示创 建成功,这样就可以保证在数据库创建完成的同时还能成功创建 Book 表。

86:

Contentprovider:

另一个程序跨进程访问:getContentProvider和Uri.parse来处理.

87:

打开相册:

@Override

public void onClick(View v) {

// 创建File对象,用于存储选择的照片

File outputImage = new File(Environment. getExternalStorageDirectory(), "output_image.jpg");

try {

if (outputImage.exists()) {

outputImage.delete(); }

outputImage.createNewFile(); } catch (IOException e) {

e.printStackTrace(); }

imageUri = Uri.fromFile(outputImage);

Intent intent = new Intent("android.intent.action. GET_CONTENT"); intent.setType("image/*");

intent.putExtra("crop", true);

intent.putExtra("scale", true); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);

startActivityForResult(intent, CROP_PHOTO);

}

}

}

88:

打开相机:

public void onClick(View v) {

// 创建File对象,用于存储拍照后的图片

File outputImage = new File(Environment. getExternalStorageDirectory(), "tempImage.jpg");

} });

}

try {

if (outputImage.exists()) {

outputImage.delete(); }

outputImage.createNewFile(); } catch (IOException e) {

e.printStackTrace(); }

imageUri = Uri.fromFile(outputImage);

Intent intent = new Intent("android.media.action. IMAGE_CAPTURE"); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); startActivityForResult(intent, TAKE_PHOTO); // 启动相机程序

}

}

89:

相机返回, 再裁剪,再显示:

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

switch (requestCode) {

case TAKE_PHOTO:

if (resultCode == RESULT_OK) {

Intent intent = new Intent("com.android.camera.action.CROP");

intent.setDataAndType(imageUri, "image/*"); intent.putExtra("scale", true); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);

startActivityForResult(intent, CROP_PHOTO); // 启动裁剪程序

}

break;

case CROP_PHOTO:

if (resultCode == RESULT_OK) {

try {

Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));

picture.setImageBitmap(bitmap); // 将裁剪后的照片显示出来

} catch (FileNotFoundException e) { e.printStackTrace();

} }

break; default:

break;

}

90:

91:

ListView:

92:

android线程间若需要线程池, 可以用ExecutorService.

privateExecutorService executorService = Executors.newFixedThreadPool(5);

// 引入线程池来管理多线程

privatevoidloadImage3(finalString url,finalintid) {

executorService.submit(newRunnable() {

publicvoidrun() {

}

93:listview优化:

异步加载图片基本思想:

1.      先从内存缓存中获取图片显示(内存缓冲)

2.      获取不到的话从SD卡里获取(SD卡缓冲)

3.      都获取不到的话从网络下载图片并保存到SD卡同时加入内存并显示(视情况看是否要显示)

94:

listview优化:

先从内存中加载,没有则开启线程从SD卡或网络中获取,这里注意从SD卡获取图片是放在子线程里执行的,否则快速滑屏的话会不够流畅,这是优化一。于此同时,在adapter里有个busy变量,表示listview是否处于滑动状态,如果是滑动状态则仅从内存中获取图片,没有的话无需再开启线程去外存或网络获取图片,这是优化二。ImageLoader里的线程使用了线程池,从而避免了过多线程频繁创建和销毁,有的童鞋每次总是new一个线程去执行这是非常不可取的,好一点的用的AsyncTask类,其实内部也是用到了线程池。在从网络获取图片时,先是将其保存到sd卡,然后再加载到内存,这么做的好处是在加载到内存时可以做个压缩处理,以减少图片所占内存,这是优化三。

95:

listview优化中内存优化部分:

首先限制内存图片缓冲的堆内存大小,每次有图片往缓存里加时判断是否超过限制大小,超过的话就从中取出最少使用的图片并将其移除,当然这里如果不采用这种方式,换做软引用也是可行的,二者目的皆是最大程度的利用已存在于内存中的图片缓存,避免重复制造垃圾增加GC负担,OOM溢出往往皆因内存瞬时大量增加而垃圾回收不及时造成的。只不过二者区别在于LinkedHashMap里的图片缓存在没有移除出去之前是不会被GC回收的,而SoftReference里的图片缓存在没有其他引用保存时随时都会被GC回收。所以在使用LinkedHashMap这种LRU算法缓存更有利于图片的有效命中,当然二者配合使用的话效果更佳,即从LinkedHashMap里移除出的缓存放到SoftReference里,这就是内存的二级缓存

96:

Fragment生命周期:

97:

fragment有一些新的状态。

onAttached() —— 当fragment被加入到activity时调用(在这个方法中可以获得所在的activity)。

onCreateView() —— 当activity要得到fragment的layout时,调用此方法,fragment在其中创建自己的layout(界面)。

onActivityCreated() —— 当activity的onCreated()方法返回后调用此方法

onDestroyView() —— 当fragment中的视图被移除的时候,调用这个方法。

onDetach() —— 当fragment和activity分离的时候,调用这个方法。

98:

将Fragment添加到返回栈中:

transaction.addToBackStack(null);

99:

Activity的状态注意点:

(1)onCreat是activity正在被创建,也就是说此时的UI操作不会更新UI,比如setText操作,所以此时在子线程调用setText不会报线程错误。详解可见Android子线程更新View的探索,在这个方法内我们可以做一些初始化工作。

(2)onRestart需要注意的是:activity正在重新启动,一般情况下,activity从不可见状态到可见状态,onRestart才会被调用,但是一定要注意的是一般来说这是用户行为导致activity不可见的时候,此时变为可见的时候才会调用onRestart,这里所说的用户行为就是用户按home键,或者进入“新”的activity。这样的操作会使activity先执行onPause,后执行onStop,这样回到这个activity会调用onRestart。为什么我这里强调说用户行为导致的不可见状态,等下我会说。。。。

(3)onStart的时候,activity才可见,但是没有出现在前台,无法与用户交互

(4)onResume的时候,activity已经可见,并且出现在前台开始活动,与onStart相比,activity都已经可见,但是onStart的时候activity还在后台,onResume才显示在前台

(5)onPause主要注意的是:此时的activity正在被停止,接下来马上调用onStop。特殊情况下快速回到该activity,onStop不会执行,会去执行onResume。

一般在这个生命周期内做存储数据、停止动画工作,但不能太耗时。

为什么特殊强调呢,因为该activity的onPause执行完了,才回去执行新的activity的onResume,一旦耗时,必然会拖慢新的activity的显示。

(6)onStop:此时的activity即将停止。在这里可以做稍微重量级的操作,同样也不能耗时。

(7)onDestroy:此时的activity即将被回收,在这里会做一些回收工作和最终资源释放。

100:

异常情况下activity被杀死,而后被重新创建的情况:

当系统停止activity时,它会调用onSaveInstanceState()(过程1),如果activity被销毁了,但是需要创建同样的实例,系统会把过程1中的状态数据传给onCreate()和onRestoreInstanceState(),所以我们要在onSaveInstanceState()内做保存参数的动作,在onRestoreInstanceState()做获取参数的动作。

101:

(1)onAttach:onAttach()回调将在Fragment与其Activity关联之后调用。需要使用Activity的引用或者使用Activity作为其他操作的上下文,将在此回调方法中实现。

需要注意的是:将Fragment附加到Activity以后,就无法再次调用setArguments()——除了在最开始,无法向初始化参数添加内容。

(2)onCreate(Bundle savedInstanceState):此时的Fragment的onCreat回调时,该fragmet还没有获得Activity的onCreate()已完成的通知,所以不能将依赖于Activity视图层次结构存在性的代码放入此回调方法中。在onCreate()回调方法中,我们应该尽量避免耗时操作。此时的bundle就可以获取到activity传来的参数

publicstaticMyFragmentnewInstance(int index){

MyFragment mf =newMyFragment();

Bundle args =newBundle();

args.putInt("index",index);

mf.setArguments(args);returnmf;

}

@OverridepublicvoidonCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);

Bundle args = getArguments();if(args !=null) {

mLabel = args.getCharSequence("label", mLabel);

}

}

(3)onCreateView(LayoutInflater inflater, ViewGroup container,

Bundle savedInstanceState): 其中的Bundle为状态包与上面的bundle不一样。

注意的是:不要将视图层次结构附加到传入的ViewGroup父元素中,该关联会自动完成。如果在此回调中将碎片的视图层次结构附加到父元素,很可能会出现异常。

这句话什么意思呢?就是不要把初始化的view视图主动添加到container里面,以为这会系统自带,所以inflate函数的第三个参数必须填false,而且不能出现container.addView(v)的操作。

View v = inflater.inflate(R.layout.hello_world,container,false);

(4)onActivityCreated:onActivityCreated()回调会在Activity完成其onCreate()回调之后调用。在调用onActivityCreated()之前,Activity的视图层次结构已经准备好了,这是在用户看到用户界面之前你可对用户界面执行的最后调整的地方。

强调的point:如果Activity和她的Fragment是从保存的状态重新创建的,此回调尤其重要,也可以在这里确保此Activity的其他所有Fragment已经附加到该Activity中了

(5)Fragment与Activity相同生命周期调用:接下来的onStart()\onResume()\onPause()\onStop()回调方法将和Activity的回调方法进行绑定,也就是说与Activity中对应的生命周期相同,因此不做过多介绍。

(6)onDestroyView:该回调方法在视图层次结构与Fragment分离之后调用。

(7)onDestroy:不再使用Fragment时调用。(备注:Fragment仍然附加到Activity并任然可以找到,但是不能执行其他操作)

(8)onDetach:Fragme生命周期最后回调函数,调用后,Fragment不再与Activity绑定,释放资源。

102:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 我们身边总是会有一些喜欢吃肉的朋友,比如说我们去吃火锅,有的人喜欢吃素菜,有的则喜欢荤菜,我就是属于后者的,有时候...
    何家小厨阅读 502评论 6 4
  • 这是小来的第38篇原创文章 很多年前我就知道一个神奇的工具,可惜那时没爱上它,可是自从上个月我参加了飞笔老师的认证...
    毅菲阅读 782评论 6 10
  • 我认识一个女孩,很单纯,很善良,却很爱哭。 她看到楼下在垃圾桶里捡垃圾的爷爷奶奶会难过,她看到整修操场的叔叔累的躺...
    丽兮同学阅读 269评论 0 0
  • 行前准备篇: 1.行前准备时,切记带好牙刷,牙膏,洗发膏,沐浴露,肥皂,剃须刀,防晒霜,太阳镜,拖鞋,因为这些船上...
    许敏雅兰阅读 1,760评论 0 0