Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all 5930 articles
Browse latest View live

15.SeekBar拖动条的应用实例

$
0
0

目标效果

在该页面中放一个拖动条的状态提示信息,一个拖动条以及一个显示拖动条值的信息。当我们点击拖动条时,在状态栏显示:正在拖动,并显示此时拖动条的值;当停止点击拖动条的时候,状态显示:停止拖动。
目标界面如下所示:
开始界面
此时停止点击拖动条
这里写图片描述

页面布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity"
    android:background="@drawable/b1" >

    <TextView 
        android:id="@+id/status"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="我是当前拖动条的状态"
        android:textColor="#FFFFFF"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="20dp"
        android:textSize="24dp"/>

    <SeekBar
        android:id="@+id/seek"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="20dp" />

    <TextView 
        android:id="@+id/show_values"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="20dp"
        android:text="当前的值为0"
        android:textColor="#FFFFFF"
        android:textSize="24dp"/>
</LinearLayout>

动作响应事件

package com.example.seekbar;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;

public class MainActivity extends Activity 
{
    TextView status,show;
    SeekBar seek=null;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        status=(TextView) findViewById(R.id.status);
        show=(TextView) findViewById(R.id.show_values);
        seek=(SeekBar) findViewById(R.id.seek);

        //为拖动条添加事件监听
        seek.setOnSeekBarChangeListener(new OnSeekBarChangeListener()
        {

            @Override
            public void onStopTrackingTouch(SeekBar arg0) 
            {
                //当检测到拖动条停止滑动时的一个方法
                status.setText("停止拖动");
            }

            @Override
            public void onStartTrackingTouch(SeekBar arg0) 
            {
                //当检测到拖动条开始滑动,第一次滑动
                status.setText("开始滑动");
            }

            @Override
            public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) 
            {
                //当检测到拖动条开始位置改变的一个方法,其中arg1表示当前滑动的values
                status.setText("正在滑动");
                show.setText("当前的值为:"+arg1);
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

}
作者:qq_36631076 发表于2017/10/18 10:13:49 原文链接
阅读:16 评论:0 查看评论

16.RatingBar星级评分条的应用实例:五星评价

$
0
0

描述

在该页面中,为我们的小狗狗打分,根据分值显示相应的信息。
目标页面效果:
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

页面布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg26"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:layout_marginLeft="10dp"
        android:text="请给今天的好狗狗打分~"
        android:textSize="24dp"/>
    <RatingBar 
        android:id="@+id/star"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_marginLeft="10dp"
        android:numStars="5"/>
    <TextView 
        android:id="@+id/show"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="汪~"
        android:textSize="24dp"
        android:layout_marginTop="10dp"
        android:layout_marginLeft="10dp"/>
</LinearLayout>

事件响应

package com.example.ratingbar;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.widget.RatingBar;
import android.widget.RatingBar.OnRatingBarChangeListener;
import android.widget.TextView;

public class MainActivity extends Activity 
{
    TextView show=null;
    RatingBar star=null;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        show=(TextView) findViewById(R.id.show);
        star=(RatingBar) findViewById(R.id.star);

        //为星级评分条添加动作监听事件
        star.setOnRatingBarChangeListener(new OnRatingBarChangeListener() 
        {

            @Override
            public void onRatingChanged(RatingBar arg0, float arg1, boolean arg2) 
            {
                //arg1表示当前评分条中选中星星的个数
                float values=arg1;
                if(values<1)
                {
                    show.setText(values+"分,掀桌子不干了!");
                }else if(values<3)
                {
                    show.setText("NO!才"+values+"分,狗狗你会加油的~");
                }else if(values<5)
                {
                    show.setText(values+"分耶~ 主人真好");
                }else{
                    show.setText("沃~ 满分,小尾巴上天!");
                }

            }
        });

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;

    }

}

作者:qq_36631076 发表于2017/10/18 10:19:44 原文链接
阅读:18 评论:0 查看评论

17.ImageSwitcher图像切换器的使用实例:查看图片

$
0
0

描述

在该实例中,提供一个图片切换器和两个点击按钮,用于切换图片,并用一个TextView显示图片信息。其中,当前图片若为最后一张,点击下一张,则跳转到第一张;同理,第一张图片点击上一张,则显示最后一张图片,循环查看当前图片。
目标效果图如下所示:
这里写图片描述
这里写图片描述
这里写图片描述

页面布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg67"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/show"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="20dp"
        android:text="我是当前图片的信息~"
        android:textSize="24dp" />


    <LinearLayout 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageSwitcher 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/image"
            android:layout_gravity="center"
            android:background="#666666">
        </ImageSwitcher>

        <LinearLayout 
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:gravity="center">

            <Button 
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="上一张"
                android:layout_marginLeft="20dp"
                android:textSize="24dp"
                android:id="@+id/up" />

            <Button 
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="下一张"
                android:layout_marginLeft="20dp"
                android:textSize="24dp"
                android:id="@+id/down" />

        </LinearLayout>

    </LinearLayout>



</LinearLayout>

事件响应

package com.example.imageswitchdemo;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.ImageSwitcher;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.ViewSwitcher.ViewFactory;

public class MainActivity extends Activity
{
    TextView show=null;
    Button up,dowm=null;
    ImageSwitcher image=null;
    private int[] images=new int[]{R.drawable.a001,R.drawable.a002,R.drawable.a003,
                                    R.drawable.a004,R.drawable.a005,R.drawable.a006,
                                    R.drawable.a007,R.drawable.a008,R.drawable.a009};
    private int index=0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //获取控件
        show=(TextView) findViewById(R.id.show);
        up=(Button) findViewById(R.id.up);
        dowm=(Button) findViewById(R.id.down);
        image=(ImageSwitcher) findViewById(R.id.image);

        //为获取到的控件添加显示效果:淡入动画和淡出动画
        image.setInAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_in));
        image.setOutAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_out));

        //为图像切换器设置一个ViewFactory,并重写makeView方法
        image.setFactory(new ViewFactory()
        {

            @Override
            public View makeView()
            {
                //指定视图切换工程
                return new ImageView(MainActivity.this);
            }
        });
        image.setImageResource(images[index]);
        show.setText("一共有"+images.length+"张图片,当前是第"+(index+1)+"张图片");

        //当点击按钮时,图像切换并显示相应的信息
        up.setOnClickListener(new OnClickListener()
        {

            @Override
            public void onClick(View arg0)
            {
                if(index>0)
                    index--;
                else
                    index=images.length-1;

                image.setImageResource(images[index]);
                show.setText("一共有"+images.length+"张图片,当前是第"+(index+1)+"张图片");
            }
        });

        //同理,当点击按钮时,图像切换并显示相应的信息
        dowm.setOnClickListener(new OnClickListener()
        {
            public void onClick(View arg0)
            {
                if(index<images.length-1)
                    index++;
                else
                    index=0;

                image.setImageResource(images[index]);
                show.setText("一共有"+images.length+"张图片,当前是第"+(index+1)+"张图片");
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

}
作者:qq_36631076 发表于2017/10/18 10:28:34 原文链接
阅读:17 评论:0 查看评论

android intent打开各种格式文档方法

$
0
0

我们开发的时候经常碰到打开各种文档,目前的应用处理方式 基本都是依赖于三方软件打开 ,而不是在应用内打开,因为文件格式有很多,倘若都在应用内打开的话,肯定要增加很大的开发时间和开发成本,而且实现效果没有一些三方的app实现的效果好。

话不多说,贴上代码

工具类


import java.io.File;

import android.content.Intent;
import android.net.Uri;

/**
 * android Intent打开各种类型文件((PDF、word、excel、ppt、chm)
 */
public class IntentDocumentView {
	// android获取一个用于打开PPT文件的intent
	public static Intent getPptFileIntent(String param) {
		Intent intent = new Intent("android.intent.action.VIEW");
		intent.addCategory("android.intent.category.DEFAULT");
		intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		Uri uri = Uri.fromFile(new File(param));
		intent.setDataAndType(uri, "application/vnd.ms-powerpoint");
		return intent;
	}

	// android获取一个用于打开Excel文件的intent
	public static Intent getExcelFileIntent(String param) {
		Intent intent = new Intent("android.intent.action.VIEW");
		intent.addCategory("android.intent.category.DEFAULT");
		intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		Uri uri = Uri.fromFile(new File(param));
		intent.setDataAndType(uri, "application/vnd.ms-excel");
		return intent;
	}

	// android获取一个用于打开Word文件的intent
	public static Intent getWordFileIntent(String param) {
		Intent intent = new Intent("android.intent.action.VIEW");
		intent.addCategory("android.intent.category.DEFAULT");
		intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		Uri uri = Uri.fromFile(new File(param));
		intent.setDataAndType(uri, "application/msword");
		return intent;
	}

	// android获取一个用于打开CHM文件的intent
	public static Intent getChmFileIntent(String param) {
		Intent intent = new Intent("android.intent.action.VIEW");
		intent.addCategory("android.intent.category.DEFAULT");
		intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		Uri uri = Uri.fromFile(new File(param));
		intent.setDataAndType(uri, "application/x-chm");
		return intent;
	}

	// android获取一个用于打开文本文件的intent
	public static Intent getTextFileIntent(String param, boolean paramBoolean) {
		Intent intent = new Intent("android.intent.action.VIEW");
		intent.addCategory("android.intent.category.DEFAULT");
		intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		if (paramBoolean) {
			Uri uri1 = Uri.parse(param);
			intent.setDataAndType(uri1, "text/plain");
		} else {
			Uri uri2 = Uri.fromFile(new File(param));
			intent.setDataAndType(uri2, "text/plain");
		}
		return intent;
	}

	// android获取一个用于打开PDF文件的intent
	public static Intent getPdfFileIntent(String param) {
		Intent intent = new Intent("android.intent.action.VIEW");
		intent.addCategory("android.intent.category.DEFAULT");
		intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		Uri uri = Uri.fromFile(new File(param));
		intent.setDataAndType(uri, "application/pdf");
		return intent;
	}

	// android获取一个用于打开图片文件的intent
	public static Intent getPicturefFileIntent(String param) {
		Intent intent = new Intent("android.intent.action.VIEW");
		intent.addCategory("android.intent.category.DEFAULT");
		intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		Uri uri = Uri.fromFile(new File(param));
		intent.setDataAndType(uri, "image/*");
		return intent;
	}

	// android获取一个用于打开压缩包的intent (手机需安装能打开压缩文件的相关软件)
	public static Intent getZipRarFileIntent(String param) {
		Intent intent = new Intent("android.intent.action.VIEW");
		intent.addCategory("android.intent.category.DEFAULT");
		intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		Uri uri = Uri.fromFile(new File(param));
		intent.setDataAndType(uri, "application/x-gzip");
		return intent;
	}

}


上面这个是核心的工具类,就是打开各种文档的跳转写法,然后在对应的点击事件调用 就可以,举列

	protected void openFile(String path) {
		
		String format = path.substring(path.lastIndexOf(".") + 1);
		File file = new File(path);
		try {
			if (file.exists() || FileUtil.fileAvailable(file)) {
				if (TextUtils.equals("doc", format) || TextUtils.equals("docx", format)) {
					mContext.startActivity(IntentDocumentView.getWordFileIntent(path));
				} else if (TextUtils.equals("xls", format) || TextUtils.equals("xlsx", format)) {
					mContext.startActivity(IntentDocumentView.getExcelFileIntent(path));
				} else if (TextUtils.equals("zip", format) || TextUtils.equals("rar", format)) {
					mContext.startActivity(IntentDocumentView.getZipRarFileIntent(path));
				}else if (TextUtils.equals("pdf", format) || TextUtils.equals("PDF", format)) {
					mContext.startActivity(IntentDocumentView.getPdfFileIntent(path));
				}else {
					ToastUtil.showToast(mContext, "新增文件类型,请联系软件开发商");
				}

			} else {
				ToastUtil.showToast(mContext, "请先下载文件");
			}
		} catch (Exception e) {
			e.printStackTrace();
			ToastUtil.showToast(mContext, "请先安装可以查看" + format + "格式的软件");
		}

	}


判断文件格式 ,然后调用工具类内对应的方法。

如果大家还有其他问题,可以加入我的开发群讨论交流:

开发一群:454430053开发二群:537532956

作者:shaoyezhangliwei 发表于2017/10/18 15:46:53 原文链接
阅读:1 评论:0 查看评论

自定义View之Dota2能力雷达图

$
0
0

1. 前言

最近看Dota2的比赛的时候无意在一个应用中看到来一个能力分析的雷达图,就是展示你的各方面数据。你可能看见过这个图。

这里写图片描述

2. 实现思路

  1. 继承View,复写onDraw。
  2. 确定N边形和每个边对应的角度;
  3. 确定多边形外接圆的半径以及圆心(也就是中心点)
  4. 确定每条半径上的所有点的坐标。
  5. 确定每条数据在图形上的坐标;
  6. 确定文字在图形上的位置;
  7. 采用合适的绘制方式绘制;

3. 实现

3.1 定义自定义属性

主要定义这几个属性,可以根据需要继续扩展。

    <declare-styleable name="CustomRadarChart">
        <!-- 蜘蛛网线条宽度 -->
        <attr name="radarLineWidth" format="dimension"/>
        <!-- 蜘蛛网颜色 -->
        <attr name="radarLineColor" format="color"/>
        <!-- 半径分成N段-->
        <attr name="radarLineSegments" format="integer"/>
        <!-- 文字颜色 -->
        <attr name="radarTextColor" format="color"/>
        <!-- 文字字体大小-->
        <attr name="radarTextSize" format="integer"/>
        <!-- 数据展示覆盖区域颜色 -->
        <attr name="radarCoverColor" format="color"/>
    </declare-styleable>

3.2 自定义View代码

注释代码中都有写出来,主要是看下实现的方式。没有过多的修饰和精简。

public class CustomRadarChart extends View {

    /**
     * N 边形,默认为6边形
     */
    private final int DEFAULT_PIECE_NUMBER = 7;

    /**
     * 线条宽度,默认为10px
     */
    private final int DEFAULT_LINE_WIDTH = 4;

    /**
     * 线条颜色,默认为灰色
     */
    private final int DEFAULT_LINE_COLOR = 0xffd0d6dc;

    /**
     * 半径分成N段,默认为4段,圆心算一段
     */
    private final int DEFAULT_LINE_SEGMENTS = 4;

    /**
     * 外接圆半径,默认为50px
     */
    private final int DEFAULT_RADIUS = 50;

    /**
     * 文本颜色和文本字体, 默认为黑色,10px
     */
    private final int DEFAULT_TEXT_COLOR = 0xff647d91;
    private final int DEFAULT_TEXT_SIZE = 10;

    /**
     * 覆盖面绘制颜色
     */
    private final int DEFAULT_COVER_COLOR = 0x55ced6dc;

    private int mPieceNumber = DEFAULT_PIECE_NUMBER;
    private int mRadius = DEFAULT_RADIUS;

    private int mLineWidth = DEFAULT_LINE_WIDTH;
    private int mLineColor = DEFAULT_LINE_COLOR;
    private int mLineSegments = DEFAULT_LINE_SEGMENTS;
    private int mTextColor = DEFAULT_TEXT_COLOR;
    private int mTextSize = DEFAULT_TEXT_SIZE;
    private int mCoverColor = DEFAULT_COVER_COLOR;

    private double mAverageAngle = 0;

    private Paint mRadarPaint;
    private TextPaint mTextPaint;
    private Paint mCoverPaint;
    private Path mCoverPath;

    List<RadarPoints> mRadarPointses = new ArrayList<>();
    List<RadarEntry> mRadarEntries = new ArrayList<>();
    List<PointF> mCoverPoints = new ArrayList<>();
    List<PointF> mTextPoints = new ArrayList<>();

    /**
     * 外接圆中心位置
     */
    private int mPositionX = 0;
    private int mPositionY = 0;

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

    public CustomRadarChart(Context context,
            @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomRadarChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray attributes = getContext().obtainStyledAttributes(attrs, R.styleable.CustomRadarChart);

        mLineWidth = (int) attributes.getDimension(R.styleable.CustomRadarChart_radarLineWidth, DEFAULT_LINE_WIDTH);
        mLineColor = attributes.getColor(R.styleable.CustomRadarChart_radarLineColor, DEFAULT_LINE_COLOR);
        mLineSegments = attributes.getInteger(R.styleable.CustomRadarChart_radarLineSegments, DEFAULT_LINE_SEGMENTS);
        mTextColor = attributes.getColor(R.styleable.CustomRadarChart_radarTextColor, DEFAULT_TEXT_COLOR);

        mTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                attributes.getInteger(R.styleable.CustomRadarChart_radarTextSize, DEFAULT_TEXT_SIZE), getResources().getDisplayMetrics());
        mCoverColor = (int) attributes.getColor(R.styleable.CustomRadarChart_radarCoverColor, DEFAULT_COVER_COLOR);

        init();
    }

    private void init() {
        /**
         * 蜘蛛网Paint初始化
         */
        mRadarPaint = new Paint();
        mRadarPaint.setColor(mLineColor);
        mRadarPaint.setStrokeWidth(mLineWidth);
        mRadarPaint.setAntiAlias(true);
        mRadarPaint.setStyle(Paint.Style.STROKE);

        /**
         * 文字绘制Paint初始化
         */
        mTextPaint = new TextPaint();
        mTextPaint.setColor(mTextColor);
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.setAntiAlias(true);
        mTextPaint.setStyle(Paint.Style.STROKE);

        /**
         *覆盖面绘制Paint初始化
         */
        mCoverPaint = new Paint();
        mCoverPaint.setColor(mCoverColor);
        mCoverPaint.setAntiAlias(true);
        mCoverPaint.setStyle(Paint.Style.FILL);
        mCoverPath = new Path();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mPositionX = w / 2;
        mPositionY = h / 2;
        mAverageAngle = 360.0 / mPieceNumber;

        int max = 0;
        for(RadarEntry entry : mRadarEntries) {
            Rect textBound = new Rect();
            mTextPaint.getTextBounds(entry.title, 0, entry.title.length(),
                    textBound);
            max = Math.max(textBound.width(), max);
        }
        mRadius = Math.min(w / 2 - max, h / 2);

        if (mRadarEntries==null || mRadarEntries.size()==0) {
            throw new NullPointerException("请先设置数据集");
        }
        /**
         * 计算每一条轴线上的所有点
         */
        for (int i = 0; i < mPieceNumber; i++) {
            List<PointF> pointFs = new ArrayList<>();
            for (int j = 0; j < mLineSegments; j++) {
                PointF point = new PointF();
                double percent = j * 1.0 / (mLineSegments - 1);
                point.set(getPloygonX(mAverageAngle * i, percent),
                        getPloygonY(mAverageAngle * i, percent));
                pointFs.add(point);
            }
            RadarPoints radarPoints = new RadarPoints(i, pointFs);
            mRadarPointses.add(radarPoints);
        }

        /**
         * 根据数据集计算覆盖多变形的点
         */
        for (int m = 0; m < mPieceNumber; m++) {
            PointF pointF = new PointF();
            double percent = mRadarEntries.get(m).level / 100.0;
            pointF.set(getPloygonX(mAverageAngle * m, percent),
                    getPloygonY(mAverageAngle * m, percent));
            mCoverPoints.add(pointF);
        }

        /**
         * 设置文字显示位置
         */
        for (int m = 0; m < mPieceNumber; m++) {
            PointF pointF = new PointF();
            String title = mRadarEntries.get(m).title;
            Rect textBound = new Rect();
            mTextPaint.getTextBounds(title, 0, title.length(),
                    textBound);
            float boundx = mRadarPointses.get(m).getPointFs().get(mLineSegments -1).x;
            float boundy = mRadarPointses.get(m).getPointFs().get(mLineSegments -1).y;
            if( boundx > mRadius && boundy <= mRadius) {
                pointF.set(getPloygonX(mAverageAngle * m, 1),
                        getPloygonY(mAverageAngle * m, 1) - textBound.height()*2) ;
            } else if ( boundx <= mRadius && boundy <= mRadius){
                pointF.set(getPloygonX(mAverageAngle * m, 1) - textBound.width(),
                        getPloygonY(mAverageAngle * m, 1) - textBound.height()*2);
            } else if( boundx <= mRadius && boundy > mRadius) {
                pointF.set(getPloygonX(mAverageAngle * m, 1) - textBound.width(),
                        getPloygonY(mAverageAngle * m, 1) );
            } else {
                pointF.set(getPloygonX(mAverageAngle * m, 1),
                        getPloygonY(mAverageAngle * m, 1));
            }
            mTextPoints.add(pointF);
        }
    }

    /**
     * 设置数据集,数据集的index决定位置,顺时针方向,起始角度为0度
     */
    public void setRadatEntries(List<RadarEntry> entries) {
        this.mRadarEntries = entries;
        mPieceNumber = entries.size();
        postInvalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /**
         * 绘制中心点
         */
        canvas.drawPoint(mPositionX, mPositionY, mRadarPaint);

        /**
         * 绘制蜘蛛网
         */
        for (int i = 0; i < mLineSegments; i++) {
            for (int j = 0; j < mPieceNumber - 1; j++) {
                canvas.drawLine(mRadarPointses.get(j).getPointFs().get(i).x, mRadarPointses.get(
                        j).getPointFs().get(i).y,
                        mRadarPointses.get(j + 1).getPointFs().get(i).x, mRadarPointses.get(
                                j + 1).getPointFs().get(i).y, mRadarPaint);
            }
            canvas.drawLine(mRadarPointses.get(mPieceNumber - 2).getPointFs().get(i).x,
                    mRadarPointses.get(mPieceNumber - 2).getPointFs().get(i).y,
                    mRadarPointses.get(mPieceNumber - 1).getPointFs().get(i).x, mRadarPointses.get(
                            mPieceNumber - 1).getPointFs().get(i).y, mRadarPaint);

            canvas.drawLine(mRadarPointses.get(mPieceNumber - 1).getPointFs().get(i).x,
                    mRadarPointses.get(mPieceNumber - 1).getPointFs().get(i).y,
                    mRadarPointses.get(0).getPointFs().get(i).x, mRadarPointses.get(
                            0).getPointFs().get(i).y, mRadarPaint);
        }

        /**
         * 绘制轴线
         */
        for (int k = 0; k < mPieceNumber; k++) {
            canvas.drawLine(mRadarPointses.get(k).getPointFs().get(0).x,
                    mRadarPointses.get(k).getPointFs().get(0).y,
                    mRadarPointses.get(k).getPointFs().get(mLineSegments - 1).x, mRadarPointses.get(
                            k).getPointFs().get(mLineSegments - 1).y, mRadarPaint);
        }

        /**
         * 绘制数据
         */
        if (mCoverPoints != null && mCoverPoints.size() == mPieceNumber) {
            mCoverPath.reset();
            mCoverPath.moveTo(mCoverPoints.get(0).x, mCoverPoints.get(0).y);
            for (int i = 1; i < mPieceNumber; i++) {
                mCoverPath.lineTo(mCoverPoints.get(i).x, mCoverPoints.get(i).y);
            }
            mCoverPath.close();
            canvas.drawPath(mCoverPath, mCoverPaint);
        } else {
            throw new NullPointerException("请先设置数据集");
        }

        /**
         * 绘制文字,使用StaticLayout进行换行文字的绘制
         */
        for (int i = 0; i < mPieceNumber; i++) {
            canvas.save();
            String str= mRadarEntries.get(i).title + "\r\n" + Math.floor(mRadarEntries.get(i).level*10)/10;
            StaticLayout layout = new StaticLayout(str, mTextPaint, 300,
                    Layout.Alignment.ALIGN_NORMAL, 1.0F, 0.0F, true);
            canvas.translate(mTextPoints.get(i).x,mTextPoints.get(i).y);
            layout.draw(canvas);
            canvas.restore();
        }

    }

    public float getPloygonX(double angle, double percent) {
        return Float.parseFloat(
                String.valueOf(
                        mPositionX + Math.cos(angle / 360.0 * 2 * Math.PI) * mRadius * percent));
    }

    public float getPloygonY(double angle, double percent) {
        return Float.parseFloat(String.valueOf(
                mPositionY + Math.sin(angle / 360.0 * 2 * Math.PI) * mRadius * percent));
    }

    /**
     * 雷达图数据载体
     */
    public static class RadarEntry {
        private String title;
        private Float level;

        public RadarEntry(String title, float level) {
            this.title = title;
            this.level = level;
        }
    }

    /**
     * 每一条线上的所有点集合
     */
    public class RadarPoints {
        int index;
        List<PointF> mPointFs;

        public RadarPoints(int index, List<PointF> pointFs) {
            this.index = index;
            mPointFs = pointFs;
        }

        public int getIndex() {
            return index;
        }

        public void setIndex(int index) {
            this.index = index;
        }

        public List<PointF> getPointFs() {
            return mPointFs;
        }

        public void setPointFs(List<PointF> pointFs) {
            mPointFs = pointFs;
        }
    }

}

可能画图过程中的实现方式优点粗糙,大家可以根据自己的方式,改写即可。
个人是先将每条半径上的端点全部计算出来进行保存,然后一次连接,然后把每个半径首尾端点连接,然后绘制覆盖区域,覆盖区域也是采用先计算点位置,然后连接的方式实现,最后确定文本的显示位置和文字的绘制。由于这里涉及到绘制换行文字,使用到来StaticLayout。使用方法请查阅对应API。

4. 效果

这里写图片描述

5. 源码

源码依然是上传到Github
CustomViewDemo

作者:poorkick 发表于2017/10/17 21:55:47 原文链接
阅读:1045 评论:0 查看评论

android webview 遇到android.os.FileUriExposedException错误

$
0
0
1. 在 Manifest 文件中添加:
<manifest ...>
    <application ...>
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"/>
        </provider>
    </application>
</manifest>






2. 创建 XML 文件: res/xml/provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>


3. 把获取文件URI代码替换成新的api:

...
imageUri = Uri.fromFile(createImageFile());


replace with :


File file = createImageFile();
                imageUri = FileProvider.getUriForFile(mActivity, mActivity.getPackageName() + ".provider", file);


...


作者:csharp25 发表于2017/10/18 18:36:17 原文链接
阅读:537 评论:0 查看评论

Android解析ClassLoader(二)Android中的ClassLoader

$
0
0

相关文章
Java虚拟机系列
Android系统启动系列
Android解析ClassLoader系列

前言

在上一篇文章我们学习了Java的ClassLoader,很多同学会把Java和Android的ClassLoader搞混,甚至会认为Android中的ClassLoader和Java中的ClassLoader是一样的,这显然是不对的。这一篇文章我们就来学习Android中的ClassLoader,来看看它和Java中的ClassLoader有何不同。

1.ClassLoader的类型

我们知道Java中的ClassLoader可以加载jar文件和Class文件(本质是加载Class文件),这一点在Android中并不适用,因为无论是DVM还是ART它们加载的不再是Class文件,而是dex文件,这就需要重新设计ClassLoader相关类,我们先来学习ClassLoader的类型。
Android中的ClassLoader类型和Java中的ClassLoader类型类似,也分为两种类型,分别是系统ClassLoader和自定义ClassLoader。其中系统ClassLoader包括三种分别是BootClassLoader、PathClassLoader和DexClassLoader。

1.1 BootClassLoader

Android系统启动时会使用BootClassLoader来预加载常用类,与Java中的BootClassLoader不同,它并是由C/C++代码实现,而是由Java实现的,BootClassLoade的代码如下所示。
libcore/ojluni/src/main/java/java/lang/ClassLoader.java

class BootClassLoader extends ClassLoader {
    private static BootClassLoader instance;
    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }
        return instance;
    }
...
}

BootClassLoader是ClassLoader的内部类,并继承自ClassLoader。BootClassLoader是一个单例类,需要注意的是BootClassLoader的访问修饰符是默认的,只有在同一个包中才可以访问,因此我们在应用程序中是无法直接调用的。

1.2 PathClassLoader

Android系统使用PathClassLoader来加载系统类和应用程序的类,如果是加载非系统应用程序类,则会加载data/app/目录下的dex文件以及包含dex的apk文件或jar文件,不管是加载那种文件,最终都是要加载dex文件,在这里为了方便理解,我们将dex文件以及包含dex的apk文件或jar文件统称为dex相关文件。PathClassLoader不建议开发直接使用。来查看它的代码:
libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java

public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }
    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

PathClassLoader继承自BaseDexClassLoader,很明显PathClassLoader的方法实现都在BaseDexClassLoader中。从PathClassLoader的构造方法也可以看出它遵循了双亲委托模式,不了解双亲委托模式请查看 Android解析ClassLoader(一)Java中的ClassLoader 这篇文章。
PathClassLoader的构造方法有三个参数:

  • dexPath:dex文件以及包含dex的apk文件或jar文件的路径集合,多个路径用文件分隔符分隔,默认文件分隔符为‘:’。
  • librarySearchPath:包含 C/C++ 库的路径集合,多个路径用文件分隔符分隔分割,可以为null。
  • parent:ClassLoader的parent。

1.3 DexClassLoader

DexClassLoader可以加载dex文件以及包含dex的apk文件或jar文件,也支持从SD卡进行加载,这也就意味着DexClassLoader可以在应用未安装的情况下加载dex相关文件。因此,它是热修复和插件化技术的基础。来查看它的代码,如下所示。
libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
        }
}

DexClassLoader构造方法的参数要比PathClassLoader多一个optimizedDirectory参数,参数optimizedDirectory代表什么呢?我们知道应用程序第一次被加载的时候,为了提高以后的启动速度和执行效率,Android系统会对dex相关文件做一定程度的优化,并生成一个ODEX文件,此后再运行这个应用程序的时候,只要加载优化过的ODEX文件就行了,省去了每次都要优化的时间,而参数optimizedDirectory就是代表存储ODEX文件的路径,这个路径必须是一个内部存储路径。
PathClassLoader没有参数optimizedDirectory,这是因为PathClassLoader已经默认了参数optimizedDirectory的路径为:/data/dalvik-cache。DexClassLoader 也继承自BaseDexClassLoader ,方法实现也都在BaseDexClassLoader中。

2.ClassLoader的继承关系

运行一个Android程序需要用到几种类型的类加载器呢?如下所示。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ClassLoader loader = MainActivity.class.getClassLoader();
        while (loader != null) {
            Log.d("liuwangshu",loader.toString());//1
            loader = loader.getParent();
        }
    }
}

首先我们得到MainActivity的类加载器,并在注释1处通过Log打印出来,接着打印出当前类的类加载器的父加载器,直到没有父加载器终止循环。打印结果如下所示。

10-07 07:23:02.835 8272-8272/? D/liuwangshu: dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/com.example.liuwangshu.moonclassloader-2/base.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_dependencies_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_0_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_1_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_2_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_3_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_4_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_5_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_6_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_7_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_8_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_9_apk.apk”],nativeLibraryDirectories=[/data/app/com.example.liuwangshu.moonclassloader-2/lib/x86, /vendor/lib, /system/lib]]]
10-07 07:23:02.835 8272-8272/? D/liuwangshu: java.lang.BootClassLoader@e175998

可以看到有两种类加载器,一种是PathClassLoader,另一种则是BootClassLoader。DexPathList中包含了很多apk的路径,其中/data/app/com.example.liuwangshu.moonclassloader-2/base.apk就是示例应用安装在手机上的位置。关于DexPathList后续文章会进行介绍。

和Java中的ClassLoader一样,虽然系统所提供的类加载器有3种类型,但是系统提供的ClassLoader相关类却不只3个。ClassLoader的继承关系如下图所示。

可以看到上面一共有7个ClassLoader相关类,其中有一些和Java中的ClassLoader相关类十分类似,下面简单对它们进行介绍:

  • ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能。BootClassLoader是它的内部类。
  • SecureClassLoader类和JDK8中的SecureClassLoader类的代码是一样的,它继承了抽象类ClassLoader。SecureClassLoader并不是ClassLoader的实现类,而是拓展了ClassLoader类加入了权限方面的功能,加强了ClassLoader的安全性。
  • URLClassLoader类和JDK8中的URLClassLoader类的代码是一样的,它继承自SecureClassLoader,用来通过URl路径从jar文件和文件夹中加载类和资源。
  • BaseDexClassLoader继承自ClassLoader,是抽象类ClassLoader的具体实现类,PathClassLoader和DexClassLoader都继承它。

3.BootClassLoader的创建

BootClassLoader是在何时被创建的呢?这得先从Zygote进程开始说起,不了解Zygote进程的可以查看Android系统启动流程(二)解析Zygote进程启动过程这篇文章。
ZygoteInit的main方法如下所示。
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

 public static void main(String argv[]) {
   ...
        try {
             ...
                preload(bootTimingsTraceLog);
             ... 
        }
    }

main方法是ZygoteInit入口方法,其中调用了ZygoteInit的preload方法,preload方法中又调用了ZygoteInit的preloadClasses方法,如下所示。
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

 private static void preloadClasses() {
        final VMRuntime runtime = VMRuntime.getRuntime();
        InputStream is;
        try {
            is = new FileInputStream(PRELOADED_CLASSES);//1
        } catch (FileNotFoundException e) {
            Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
            return;
        }
        ...
        try {
            BufferedReader br
                = new BufferedReader(new InputStreamReader(is), 256);//2

            int count = 0;
            String line;
            while ((line = br.readLine()) != null) {//3
                line = line.trim();
                if (line.startsWith("#") || line.equals("")) {
                    continue;
                }
                  Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
                try {
                    if (false) {
                        Log.v(TAG, "Preloading " + line + "...");
                    }
                    Class.forName(line, true, null);//4
                    count++;
                } catch (ClassNotFoundException e) {
                    Log.w(TAG, "Class not found for preloading: " + line);
                } 
        ...
        } catch (IOException e) {
            Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
        } finally {
            ...
        }
    }

preloadClasses方法用于Zygote进程初始化时预加载常用类。注释1处将/system/etc/preloaded-classes文件封装成FileInputStream,preloaded-classes文件中存有预加载类的目录,这个文件在系统源码中的路径为frameworks/base/preloaded-classes,这里列举一些preloaded-classes文件中的预加载类名称,如下所示。

android.app.ApplicationLoaders
android.app.ApplicationPackageManager
android.app.ApplicationPackageManager$OnPermissionsChangeListenerDelegate
android.app.ApplicationPackageManager$ResourceName
android.app.ContentProviderHolder
android.app.ContentProviderHolder$1
android.app.ContextImpl
android.app.ContextImpl$ApplicationContentResolver
android.app.DexLoadReporter
android.app.Dialog
android.app.Dialog$ListenersHandler
android.app.DownloadManager
android.app.Fragment

可以看到preloaded-classes文件中的预加载类的名称有很多都是我们非常熟知的。预加载属于拿空间换时间的策略,Zygote环境配置的越健全越通用,应用程序进程需要单独做的事情也就越少,预加载除了预加载类,还有预加载资源和预加载共享库,因为不是本文重点,这里就不在延伸讲下去了。
回到preloadClasses方法的注释2处,将FileInputStream封装为BufferedReader,并注释3处遍历BufferedReader,读出所有预加载类的名称,每读出一个预加载类的名称就调用注释4处的代码加载该类,Class的forName方法如下所示。
libcore/ojluni/src/main/java/java/lang/Class.java

    @CallerSensitive
    public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    {
        if (loader == null) {
            loader = BootClassLoader.getInstance();//1
        }
        Class<?> result;
        try {
            result = classForName(name, initialize, loader);//2
        } catch (ClassNotFoundException e) {
            Throwable cause = e.getCause();
            if (cause instanceof LinkageError) {
                throw (LinkageError) cause;
            }
            throw e;
        }
        return result;
    }

注释1处创建了BootClassLoader,并将BootClassLoader实例传入到了注释2处的classForName方法中,classForName方法是Native方法,它的实现由c/c++代码来完成,如下所示。

    @FastNative
    static native Class<?> classForName(String className, boolean shouldInitialize,
            ClassLoader classLoader) throws ClassNotFoundException;

4.PathClassLoader的创建

PathClassLoader的创建也得从Zygote进程开始说起,Zygote进程启动SyetemServer进程时会调用ZygoteInit的startSystemServer方法,如下所示。
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

private static boolean startSystemServer(String abiList, String socketName)
           throws MethodAndArgsCaller, RuntimeException {
    ...
        int pid;
        try {
            parsedArgs = new ZygoteConnection.Arguments(args);//2
            ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
            ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
            /*1*/
            pid = Zygote.forkSystemServer(
                    parsedArgs.uid, parsedArgs.gid,
                    parsedArgs.gids,
                    parsedArgs.debugFlags,
                    null,
                    parsedArgs.permittedCapabilities,
                    parsedArgs.effectiveCapabilities);
        } catch (IllegalArgumentException ex) {
            throw new RuntimeException(ex);
        }
       if (pid == 0) {//2
           if (hasSecondZygote(abiList)) {
               waitForSecondaryZygote(socketName);
           }
           handleSystemServerProcess(parsedArgs);//3
       }
       return true;
   }

注释1处,Zygote进程通过forkSystemServer方法fork自身创建子进程(SystemServer进程)。注释2处如果forkSystemServer方法返回的pid等于0,说明当前代码是在新创建的SystemServer进程中执行的,接着就会执行注释3处的handleSystemServerProcess方法:
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

 private static void handleSystemServerProcess(
            ZygoteConnection.Arguments parsedArgs)
            throws Zygote.MethodAndArgsCaller {

    ...
        if (parsedArgs.invokeWith != null) {
           ...
        } else {
            ClassLoader cl = null;
            if (systemServerClasspath != null) {
                cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);//1
                Thread.currentThread().setContextClassLoader(cl);
            }
            ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
        }
    }

注释1处调用了createPathClassLoader方法,如下所示。
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

  static PathClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {
      String libraryPath = System.getProperty("java.library.path");
      return PathClassLoaderFactory.createClassLoader(classPath,
                                                      libraryPath,
                                                      libraryPath,
                                                      ClassLoader.getSystemClassLoader(),
                                                      targetSdkVersion,
                                                      true /* isNamespaceShared */);
    }

createPathClassLoader方法中又会调用PathClassLoaderFactory的createClassLoader方法,看来PathClassLoader是用工厂来进行创建的。
frameworks/base/core/java/com/android/internal/os/PathClassLoaderFactory.java

  public static PathClassLoader createClassLoader(String dexPath,
                                                    String librarySearchPath,
                                                    String libraryPermittedPath,
                                                    ClassLoader parent,
                                                    int targetSdkVersion,
                                                    boolean isNamespaceShared) {
        PathClassLoader pathClassloader = new PathClassLoader(dexPath, librarySearchPath, parent);
      ...
        return pathClassloader;
    }

在PathClassLoaderFactory的createClassLoader方法中会创建PathClassLoader。

结语

在这篇文章中我们学习了Android的ClassLoader的类型、ClassLoader的继承关系以及BootClassLoader和PathClassLoader是何时创建的。BootClassLoader是在Zygote进程的入口方法中创建的,PathClassLoader则是在Zygote进程创建SystemServer进程时创建的。本系列后续文章会接着介绍Android中的ClassLoader的其他知识点,敬请期待。

参考资料
Android动态加载之ClassLoader详解
热修复入门:Android 中的 ClassLoader
浅析dex文件加载机制

作者:itachi85 发表于2017/10/18 19:45:14 原文链接
阅读:216 评论:0 查看评论

代理属性为何要用 weak 修饰?

$
0
0

代理模式在 IOS 开发中的使用频率非常之高,然而很多人只是单纯地惯性使用,并没有弄清楚一点——要用 weak 修饰代理属性,今天就来细说这其中的缘由。

用一个实例进行说明(一共有三个类,BabyView 类,BabySitterView 类,ViewController 类,假设 BabyView 类想要做一些自己无法做的事情,就必须具有代理的属性,因此指定一个协议,而 BabySitterView 需要成为 BabyView 的代理,必须遵循协议,直接上代码截图):

BabyView 类:
这里写图片描述

BabySitter 类:
这里写图片描述

ViewController 类:
这里写图片描述

若使用 weak 修饰代理属性,则控制台打印输出为:
这里写图片描述
表示对象被顺利地销毁,调用了 Baby 类和 BabySitter 类的 delloc 方法,若使用 strong 修饰代理属性,则控制台没有打印,这表明造成了循环引用,对象不能正常释放。

图解:
这里写图片描述
若用 weak 修饰代理属性,则不会造成循环引用,而如果换为 strong 修饰代理属性,则图中的 BabyView 类向 Delegate 的连线由虚线变为实现,会造成循环引用,导致内存泄露。

代理属性为何要用 weak 修饰?
一句话来解决这个问题,就是为了避免循环引用。

作者:huangfei711 发表于2017/10/19 11:25:37 原文链接
阅读:28 评论:0 查看评论

Android仿拼多多拼团堆叠头像

$
0
0

序言

做电商的都知道,作为商品的一种售卖方式,拼团是是提供商品售卖的一种及时有效的方式,而在拼团市场中,拼多多无疑是做的最好的一家。于是,研究拼多多的售卖方式之后,我们的产品也开始了这方面的开发。本文将要给大家介绍的就是通过自定义的方式实现堆叠头像,这种效果在直播app中非常常见。下面是部分效果:
这里写图片描述

通过分析,上面是一个使用ViewPager实现的一个可以左右无线循环的Galllery,相关实现可以访问我之前的介绍:PageTransformer使用简介
下面是一个列表的方式,可以通过下拉来加载更多的Cell数据,也比较简单。对于组合头像的实现也是比较简单的,其实就是一个简单的流式布局,在本篇实现上,本文也参考了张鸿洋的FlowLayout,对于流式布局来说,只要按照某种线性规则依次排列即可。

我相信很多朋友之前一定遇到过这种需求:富文本自动换行,如下所示:
这里写图片描述
要实现这种富文本换行,最重要的就是对onMeasure方法,通常的做法是,测量出子View的宽度,当大于屏幕的宽度的时候就换行(当然,需要考虑文字本来就很长,一行显示不下的情况)。相关代码如下:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);


        //AT_MOST
        int width = 0;
        int height = 0;
        int rawWidth = 0;//当前行总宽度
        int rawHeight = 0;// 当前行高

        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if(child.getVisibility() == GONE){
                if(i == count - 1){
                    //最后一个child
                    height += rawHeight;
                    width = Math.max(width, rawWidth);
                }
                continue;
            }

            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

            int childWidth = child.getMeasuredWidth()  + lp.leftMargin + lp.rightMargin;
            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            Log.e("=====", "childWidth 1: " + childWidth);
            if(rawWidth + childWidth > widthSpecSize - getPaddingLeft() - getPaddingRight()){
                //换行
                width = Math.max(width, rawWidth);
                rawWidth = childWidth;
                height += rawHeight;
                rawHeight = childHeight;
            } else {
                rawWidth += childWidth;
                rawHeight = Math.max(rawHeight, childHeight);
            }

            if(i == count - 1){
                width = Math.max(rawWidth, width);
                height += rawHeight;
            }
        }

        setMeasuredDimension(
                widthSpecMode == MeasureSpec.EXACTLY ? widthSpecSize : width + getPaddingLeft() + getPaddingRight(),
                heightSpecMode == MeasureSpec.EXACTLY ? heightSpecSize : height + getPaddingTop() + getPaddingBottom()
        );
    }

实现

说了这么多,那么具体怎么实现的呢?由于时间关系,这里我就直接贴代码了。首先自定义ViewGrop,实现后一个头像会覆盖一部分到前一个头像上,为了方便使用者控制堆叠头像的重叠大小,我们通过自定义属性来解决。

PileView.java

public class PileView extends ViewGroup {

    protected float vertivalSpace;//垂直间隙
    protected float pileWidth=0;//重叠宽度

    public PileView(Context context) {
        this(context, null, 0);
    }

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

    public PileView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttr(context, attrs);
    }

    private void initAttr(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PileLayout);
        vertivalSpace = ta.getDimension(R.styleable.PileLayout_PileLayout_vertivalSpace, dp2px(4));
        pileWidth = ta.getDimension(R.styleable.PileLayout_PileLayout_pileWidth, dp2px(10));
        ta.recycle();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        //AT_MOST
        int width = 0;
        int height = 0;
        int rawWidth = 0;//当前行总宽度
        int rawHeight = 0;// 当前行高

        int rowIndex = 0;//当前行位置
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if(child.getVisibility() == GONE){
                if(i == count - 1){
                    //最后一个child
                    height += rawHeight;
                    width = Math.max(width, rawWidth);
                }
                continue;
            }

            //调用measureChildWithMargins 而不是measureChild
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

            int childWidth = child.getMeasuredWidth()  + lp.leftMargin + lp.rightMargin;
            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            if(rawWidth + childWidth  - (rowIndex > 0 ? pileWidth : 0)> widthSpecSize - getPaddingLeft() - getPaddingRight()){
                //换行
                width = Math.max(width, rawWidth);
                rawWidth = childWidth;
                height += rawHeight + vertivalSpace;
                rawHeight = childHeight;
                rowIndex = 0;
            } else {
                rawWidth += childWidth;
                if(rowIndex > 0){
                    rawWidth -= pileWidth;
                }
                rawHeight = Math.max(rawHeight, childHeight);
            }

            if(i == count - 1){
                width = Math.max(rawWidth, width);
                height += rawHeight;
            }

            rowIndex++;
        }

        setMeasuredDimension(
                widthSpecMode == MeasureSpec.EXACTLY ? widthSpecSize : width + getPaddingLeft() + getPaddingRight(),
                heightSpecMode == MeasureSpec.EXACTLY ? heightSpecSize : height + getPaddingTop() + getPaddingBottom()
        );
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int viewWidth = r - l;
        int leftOffset = getPaddingLeft();
        int topOffset = getPaddingTop();
        int rowMaxHeight = 0;
        int rowIndex = 0;//当前行位置
        View childView;
        for( int w = 0, count = getChildCount(); w < count; w++ ){
            childView = getChildAt(w);
            if(childView.getVisibility() == GONE) continue;

            MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
            // 如果加上当前子View的宽度后超过了ViewGroup的宽度,就换行
            int occupyWidth = lp.leftMargin + childView.getMeasuredWidth() + lp.rightMargin;
            if(leftOffset + occupyWidth + getPaddingRight() > viewWidth){
                leftOffset = getPaddingLeft();  // 回到最左边
                topOffset += rowMaxHeight + vertivalSpace;  // 换行
                rowMaxHeight = 0;

                rowIndex = 0;
            }

            int left = leftOffset + lp.leftMargin;
            int top = topOffset + lp.topMargin;
            int right = leftOffset+ lp.leftMargin + childView.getMeasuredWidth();
            int bottom =  topOffset + lp.topMargin + childView.getMeasuredHeight();
            childView.layout(left, top, right, bottom);

            // 横向偏移
            leftOffset += occupyWidth;
            // 试图更新本行最高View的高度
            int occupyHeight = lp.topMargin + childView.getMeasuredHeight() + lp.bottomMargin;
            if(rowIndex != count - 1){
                leftOffset -= pileWidth;
            }
            rowMaxHeight = Math.max(rowMaxHeight, occupyHeight);
            rowIndex++;
        }
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new MarginLayoutParams(p);
    }

    public float dp2px(float dpValue) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics());
    }
}

自定义的属性如下:

<declare-styleable name="PileLayout">
        <attr name="PileLayout_vertivalSpace" format="dimension"/>
        <attr name="PileLayout_pileWidth" format="dimension"/>
    </declare-styleable>

为了方便用户使用,我们在PileView的基础上再封装一下,封装完成后,只需要用户提供数据源即可实现头像堆叠。例如,下面的代码给控件设置数据源即可:

List<String> urls=new ArrayList<>();
        urls.clear();
        urls.add("http://ohe65w0xx.bkt.clouddn.com/u=2263418180,3668836868&fm=206&gp=0.jpg");
        urls.add("http://ohe65w0xx.bkt.clouddn.com/u=3637404049,2821183587&fm=214&gp=0.jpg");
       urls.add("http://ohe65w0xx.bkt.clouddn.com/avert.png");
//设置数据源
itemAvertView.setAvertImages(urls);

要完成上面的封装,主要会涉及如下的代码:
PileAvertView.java

public class PileAvertView extends LinearLayout {

    @BindView(R.id.pile_view)
    PileView pileView;

    private Context context = null;
    public static final int VISIBLE_COUNT = 3;//默认显示个数

    public PileAvertView(Context context) {
        this(context, null);
        this.context = context;
    }

    public PileAvertView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        initView();
    }

    private void initView() {
        View view = LayoutInflater.from(context).inflate(R.layout.layout_group_pile_avert, this);
        ButterKnife.bind(view);
    }

    public void setAvertImages(List<String> imageList) {
        setAvertImages(imageList,VISIBLE_COUNT);
    }

    //如果imageList>visiableCount,显示List最上面的几个
    public void setAvertImages(List<String> imageList, int visibleCount) {
        List<String> visibleList = null;
        if (imageList.size() > visibleCount) {
            visibleList = imageList.subList(imageList.size() - 1 - visibleCount, imageList.size() - 1);
        }
        pileView.removeAllViews();
        for (int i = 0; i < imageList.size(); i++) {
            CircleImageView image= (CircleImageView) LayoutInflater.from(context).inflate(R.layout.item_group_round_avert, pileView, false);
            CommonImageUtil.loadImage(imageList.get(i), image);
            pileView.addView(image);
        }
    }

}

相关的布局:
layout_group_pile_avert.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal">

    <com.lanshan.shihuicommunity.grouppurchase.view.PileView
        android:id="@+id/pile_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:PileLayout_pileWidth="10dp"/>
</LinearLayout>

item_group_round_avert.xml

<?xml version="1.0" encoding="utf-8"?>
<com.makeramen.rounded.CircleImageView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/circle_iamge"
    android:layout_width="30dp"
    android:layout_height="30dp"
    android:orientation="vertical"
    app:round_borderColor="#ffffff"
    app:round_borderWidth="1dp" />
作者:xiangzhihong8 发表于2017/10/19 11:16:06 原文链接
阅读:58 评论:0 查看评论

Andorid 换肤框架AndSkin源码解析及优缺点

$
0
0

AndSkin简介及使用教程

AndSkin gaybug: https://github.com/RrtoyewxXu/andSkin

AndSkin 作者写的说明: http://blog.csdn.net/zhi184816/article/details/53436761

AndSkin源码解析

初始化

BaseSkinApplication解析

BaseSkinApplication中其实就一行初始化的代码:SkinLoader.getDefault().init(this)。其中SkinLoader.getDefault()采用单例返回了SkinLoader对象,中间啥也没干,故省去这部分代码。

    public void init(Context context) {
        mLoadSkinDeliver = new LoadSkinDeliver();
        DataManager.getDefault().init(context, mLoadSkinDeliver);

        String pluginAPKPackageName = DataManager.getDefault().getPluginPackageName();
        String pluginAPKPath = DataManager.getDefault().getPluginPath();
        String pluginAPKSuffix = DataManager.getDefault().getResourceSuffix();

        GlobalManager.getDefault().init(context, pluginAPKPackageName, pluginAPKPath, pluginAPKSuffix);
        ResourceManager.getDefault().init(pluginAPKPackageName, pluginAPKPath, pluginAPKSuffix, mLoadSkinDeliver);
    }

初始化的代码基本都在这里。后续会用到这里初始化的很多对象,所以下面会比较详细的讲解这个方法及中间生成的各种对象。

LoadSkinDeliver是SkinLoader的内部类,继承自IDeliver,而且内部有个获取了主线程looper的Handler,功能是负责消息的分发。在后续的换肤操作消息分发都由LoadSkinDeliver负责通知到各个界面的各个View。

    private class LoadSkinDeliver implements IDeliver {
        private Handler mHandler = new Handler(Looper.getMainLooper());
        ...
    }

DataManager实现了ILoadSkin接口,实际上这个类的作用只是采用SP的方式保存下当前皮肤的后缀(suffix),对于动态换肤还会保存插件包包名(plugin_package_name)和插件包路径(plugin_path)。作用相当于SP的封装类,相信读者能够看快看透。这里就不详细讲述了。

GlobalManager这个类就是个JavaBean,在内存中保存了ApplicationContext、PackageName、PluginAPKPackageName、PluginAPKPath和ResourceSuffix属性。除此之外,没有任何作用。

ResourceManager比较重要,也是动态换肤的精髓所在。ResourceManager实现了ILoadSkin接口,并持有一个Resource的引用。

public class ResourceManager implements ILoadSkin {
    private Resource mResource;
    private IDeliver mIDeliver;
    ...
    void init(String pluginPackageName, String pluginPath, String pluginSuffix, IDeliver deliver) {
        mIDeliver = deliver;
        smartCreateResource(pluginPackageName, pluginPath, pluginSuffix, true);
    }

    private boolean smartCreateResource(String pluginPackageName, String pluginPath, String suffix, boolean firstInit) {
        boolean shouldCreate = checkIfReCreateDateResource(pluginPackageName, pluginPath, suffix);
        if (shouldCreate) {
            try {
                createDataResource(pluginPackageName, pluginPath, suffix);
                mIDeliver.postResourceManagerLoadSuccess(firstInit, pluginPackageName, pluginPath, suffix);
            } catch (Exception e) {
                e.printStackTrace();
                mIDeliver.postResourceManagerLoadError(firstInit);
            }
        } else {
            mResource.changeResourceSuffix(suffix);
            mIDeliver.postResourceManagerLoadSuccess(false, pluginPackageName, pluginPath, suffix);
        }

        return mResource != null;
    }
}

smartCreateResource方法中首先会依据pluginPackageName, pluginPath, suffix判断是否需要重新生成Resource对象,这里依据是本地换肤还是动态换肤生成相应的LocalResource或者PluginResource对象。这里只是初始化,暂时不用深入,后续换肤章节中会详细解析这部分。目前是第一次实例化,mResource==null,即shouldCreate为true。

    private void createDataResource(String pluginPackageName, String pluginPath, String suffix) throws Exception {
        mResource = ResourceFactory.newInstance().createResource(pluginPackageName, pluginPath, suffix, mIDeliver);
    }
public abstract class ResourceFactory {
    private ResourceFactory() {
    }

    public static ResourceFactory newInstance() {
        return new ResourceFactoryImp();
    }

    public abstract Resource createResource(String pluginPackageName, String pluginPath, String suffix, IDeliver deliver) throws Exception;


    static class ResourceFactoryImp extends ResourceFactory {
        private ResourceFactoryImp() {

        }

        @Override
        public Resource createResource(String pluginPackageName, String pluginPath, String suffix, IDeliver deliver) throws Exception {
            String packageName = GlobalManager.getDefault().getPackageName();
            Context context = GlobalManager.getDefault().getApplicationContext();

            if (!TextUtils.isEmpty(pluginPackageName) && !pluginPackageName.equals(packageName)) {
                return new PluginResource(context, pluginPackageName, pluginPath, suffix);
            }
            return new LocalResource(context, pluginPackageName, pluginPath, suffix);
        }
    }

}

mResource属性会在这里初始化,生成的逻辑也很简单,就是依据报名判断是不是动态换肤。如果不是,返回PluginResource,反之则返回LocalResource。这两个类都继承自Resource,并重写了部分方法,差别是PluginResource实例化时传入了动态插件(.apk)的AssetManager,这个AssetManager使用动态插件的路径构建的。这部分仍在会在换肤章节重点解析,现在回到ResourceManager#smartCreateResource。在获取到Resource之后,调用mIDeliver.postResourceManagerLoadSuccess分发消息到监听器中,告诉监听器初自己始化完毕。代码体现如下:

            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (firstInit && mOnInitLoadSkinResourceListener != null) {
                        mOnInitLoadSkinResourceListener.onInitResourceSuccess();
                    } else {
                        boolean findResourceSuccess = notifyAllChangeSkinObserverListToFindResource();

                        if (findResourceSuccess) {
                            postGetAllResourceSuccessOnMainThread(pluginPackageName, pluginPath, resourceSuffix);
                        } else {
                            postGetResourceErrorOnMainThread();
                        }
                    }
                }
            });

换肤

换肤需要换肤的Activity继承自BaseSkinActivity。BaseSkinActivity继承自AppCompatActivity,并实现了IChangeSkin接口。核心代码如下:

public class BaseSkinActivity extends AppCompatActivity implements IChangeSkin {
    protected BaseSkinActivity mActivity;
    private SkinLayoutInflater mSkinLayoutInflater;
    ...

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        mActivity = this;
        if (shouldRegister()) {
            mSkinLayoutInflater = new SkinLayoutInflater(this);
        }
        super.onCreate(savedInstanceState);
    }

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        super.setContentView(layoutResID);
        if (shouldRegister()) {
            findLayoutInflaterSkinViews();
            generateStatusBarIfShould();
            SkinLoader.getDefault().register(mActivity);
        }
    }

由于LayoutInflaterCompat.setFactory只有在第一次调用的时候有效,所以AppCompatActivity#installViewFactory(在onCreate被调用,方法内会调用LayoutInflaterCompat.setFactory)在被调用之前提前调用LayoutInflaterCompat.setFactory。代码体现为:

public class SkinLayoutInflater {
    ...
    public SkinLayoutInflater(BaseSkinActivity baseSkinActivity) {
        this.mBaseSkinActivity = baseSkinActivity;
        mSkinInflaterFactory = new SkinInflaterFactory();

        mDynamicAddSkinViewList = new ArrayList<>();
        mLayoutInflaterSkinViewList = new ArrayList<>();

        LayoutInflaterCompat.setFactory(mBaseSkinActivity.getLayoutInflater(), mSkinInflaterFactory);
    }
    ...
}

这里的SkinInflaterFactory实现了LayoutInflaterFactory接口,如此一来,在继承BaseSkinActivity的页面中,View从XML到变成View的解析工作就交给了SkinInflaterFactory。

下面回到BaseSkinActivity#setContentView。在调用super.setContentView(layoutResID)之后会调用findLayoutInflaterSkinViews方法。而在View从XML变成View之前,会调用SkinInflaterFactory#onCreateView。这部分逻辑体现在framework层的LayoutInflater#createViewFromTag。感兴趣的可以查看我的另一篇博文 Android XML布局文件解析过程源码解析

SkinInflaterFactory中有个属性mSkinViewList,其中保存了所有需要换肤View的id。接下来会分析怎么获取到的这些id,这也是换肤框架的一个核心难点所在。

public class SkinInflaterFactory implements LayoutInflaterFactory {
    private List<SkinView> mSkinViewList = new ArrayList<>();


    @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        boolean isSkinEnable = attrs.getAttributeBooleanValue(ConfigConstants.SKIN_NAMES_SPACE, ConfigConstants.ATTR_SKIN_ENABLE, false);
        String attrList = attrs.getAttributeValue(ConfigConstants.SKIN_NAMES_SPACE, ConfigConstants.ATTR_SKIN_LIST);
        if (isSkinEnable) {
            try {
                if (TextUtils.isEmpty(attrList)) {
                    parseSkinAttr(context, attrs, name);
                } else {
                    attrList = attrList.trim();
                    parseSkinAttrByAttrList(context, attrs, attrList, name);
                }
            } catch (Exception e) {
                e.printStackTrace();
                SkinL.e("解析xml文件失败,请检查xml文件");
            }
        }
        return null;
    }
    ...
}

先看下大概的逻辑:

SKIN_NAMES_SPACE是常量:http://schemas.android.com/android/andSkin
ATTR_SKIN_ENABLE是常量:enable
ATTR_SKIN_LIST是常量:attrs

首先查看View是否设置了enable属性。如果没有,则不需要换肤。如果有,再去判断哪些属性需要换肤。这里能够获取View所有的属性及对应的值,所以查找及添加到mSkinViewList的代码就不一一解析了。跟普通写个list.add()没啥区别。需要注意的地方是,这里onCreateView返回了null,也就是说创建View的工作交给了LayoutInflater#onCreateView。还一个需要注意的是,这里保存的是View的id,并不是真正的View的引用。很明显,返回null的话,View这时候还没创建。

所以初始化及准备工作到这里就算真正完成了,接下来是真正调用换肤API实现换肤的解析。

本地换肤

假设我现在有两套皮肤,一套叫day,另一套叫night。day皮肤为默认皮肤,文件命名大概为:icon_search.png。night为夜间皮肤,文件命名大概为icon_search_night.png。那么,应用内由day皮肤切换到night的代码为:

SkinLoader.getDefault().loadSkin("night");

一行代码实现换肤,使用非常简单。跟进。

public class SkinLoader implements ILoadSkin {
    ...
    @Override
    public void loadSkin(String suffix) {
        loadSkin("", "", suffix);
    }

    @Override
    public void loadSkin(String pluginPackageName, String pluginPath, String suffix) {
        loadSkinInner(pluginPackageName, pluginPath, suffix, true);
    }

    private void loadSkinInner(String pluginPackageName, String pluginPath, String suffix, boolean needCallSkinChangeListener) {
        cancelLoadSkinTask();
        startLoadSkinTask(pluginPackageName, pluginPath, suffix, needCallSkinChangeListener);
    }

    private void startLoadSkinTask(String pluginAPKPackageName, String pluginAPKPath, String resourceSuffix, boolean needCallSkinChangeListener) {
        mLoadSkinTask = new LoadSkinTask();
        mLoadSkinTask.setNeedCallSkinChangeListener(needCallSkinChangeListener);
        mLoadSkinTask.execute(pluginAPKPackageName, pluginAPKPath, resourceSuffix);
    }
    ...
}

经过一些列的重载进入到startLoadSkinTask方法中。LoadSkinTask继承自AsyncTask,实例化mLoadSkinTask属性之后,设置needCallSkinChangeListener为true。最后调用execute()方法,并将pluginAPKPackageName、pluginAPKPath,、resourceSuffix传递进去。因为是本地换肤,pluginAPKPackageName和pluginAPKPath参数暂时为null。

在onPreExecute()中会通知所有的观察者换肤开始,通常观察者只有一个。在doInBackground中调用DataManager#loadSkin。跟进。

public class DataManager implements ILoadSkin {
    ...
    @Override
    public void loadSkin(String pluginPackageName, String pluginPath, String suffix) {
        if (pluginPackageName != null && pluginPackageName.equals(getPluginPackageName())
                && pluginPath != null && pluginPath.equals(getPluginPath())
                && suffix != null && suffix.equals(getResourceSuffix())) {

            mDeliver.postDataManagerLoadError();
        } else {
            savePluginPackageName(pluginPackageName);
            savePluginPath(pluginPath);
            saveResourceSuffix(suffix);
            mDeliver.postDataManagerLoadSuccess(pluginPackageName, pluginPath, suffix);
        }
    }
}

else中三个save方法都是在SP文件中保存即将要应用的皮肤的信息,主要是suffix。

        @Override
        public void postDataManagerLoadSuccess(String pluginPackageName, String pluginPath, String resourceSuffix) {
            SkinL.d("保存本次换肤的相关信息成功");
            ResourceManager.getDefault().loadSkin(pluginPackageName, pluginPath, resourceSuffix);
        }

经过在DataManager中保存SP信息之后,由LoadSkinDeliver分发消息到ResourceManager#loadSkin。

    @Override
    public void loadSkin(String pluginPackageName, String pluginPath, String suffix) {
        try {
            smartCreateResource(pluginPackageName, pluginPath, suffix, false);
        } catch (Exception e) {
            e.printStackTrace();
            mIDeliver.postResourceManagerLoadError(false);
        }
    }

初始化的时候简单分析过smartCreateResource方法,和上次不同的是,这次最后一个参数firstInit为false。

    private boolean smartCreateResource(String pluginPackageName, String pluginPath, String suffix, boolean firstInit) {
        boolean shouldCreate = checkIfReCreateDateResource(pluginPackageName, pluginPath, suffix);
        SkinL.d("should create resource : " + shouldCreate);
        if (shouldCreate) {
            try {
                createDataResource(pluginPackageName, pluginPath, suffix);
                mIDeliver.postResourceManagerLoadSuccess(firstInit, pluginPackageName, pluginPath, suffix);
            } catch (Exception e) {
                e.printStackTrace();
                mIDeliver.postResourceManagerLoadError(firstInit);
            }
        } else {
            mResource.changeResourceSuffix(suffix);
            mIDeliver.postResourceManagerLoadSuccess(false, pluginPackageName, pluginPath, suffix);
        }

        return mResource != null;
    }

由于初始化的时候,默认创建了LocalResource,所以这里shouldCreate为false,走else的逻辑。mResource.changeResourceSuffix(suffix)只是简单的记录night的后缀。之后继续由LoadSkinDeliver分发消息。

        public void postResourceManagerLoadSuccess(final boolean firstInit, final String pluginPackageName, final String pluginPath, final String resourceSuffix) {
            SkinL.d("生成Resource对象成功");

            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (firstInit && mOnInitLoadSkinResourceListener != null) {
                        mOnInitLoadSkinResourceListener.onInitResourceSuccess();
                    } else {
                        boolean findResourceSuccess = notifyAllChangeSkinObserverListToFindResource();

                        if (findResourceSuccess) {
                            postGetAllResourceSuccessOnMainThread(pluginPackageName, pluginPath, resourceSuffix);
                        } else {
                            postGetResourceErrorOnMainThread();
                        }
                    }
                }
            });
        }

这次firstInit为false。终于要走换肤的逻辑了。。。

    private boolean notifyAllChangeSkinObserverListToFindResource() {
        boolean findResourceSuccess = true;
        SkinL.d("通知所有的观察者查找资源");
        for (IChangeSkin changeSkin : mChangeSkinObserverList) {
            findResourceSuccess = changeSkin.findResource();
            if (!findResourceSuccess) {
                break;
            }
        }
        return findResourceSuccess;
    }

        @Override
        public void postGetAllResourceSuccessOnMainThread(String pluginPackageName, String pluginPath, String resourceSuffix) {
            SkinL.d("查找所有资源成功");
            GlobalManager.getDefault().flushPluginInfos(pluginPackageName, pluginPath, resourceSuffix);
            notifyAllChangeSkinObserverListToApplySKin();
        }

    private void notifyAllChangeSkinObserverListToApplySKin() {
        SkinL.d("通知所有的组件进行换肤");
        for (IChangeSkin changeSkin : mChangeSkinObserverList) {
            changeSkin.changeSkin();
        }
    }

所有的BaseSkinActivity对象都会被add到mChangeSkinObserverList属性。也就是说首先会调用所有BaseSkinActivity对象的findResource方法,找到所有换肤需要的资源,之后再统一调用changeSkin。逻辑缕清了,直接顺序查看这两个方法。

public class BaseSkinActivity extends AppCompatActivity implements IChangeSkin {
    @Override
    public boolean findResource() {

        ...
        List<SkinView> layoutInflaterSkinViewList = mSkinLayoutInflater.getLayoutInflaterSkinViewList();
        for (IChangeSkin skinView : layoutInflaterSkinViewList) {
            findResourceSuccess = skinView.findResource();
            if (!findResourceSuccess) {
                break;
            }
        }
        ...
        return findResourceSuccess;
    }

    @Override
    public void changeSkin() {
        List<SkinView> layoutInflaterSkinViewList = mSkinLayoutInflater.getLayoutInflaterSkinViewList();
        for (IChangeSkin skinView : layoutInflaterSkinViewList) {
            skinView.changeSkin();
        }
    }
}

SkinView实现了IChangeSkin接口,在执行SkinInflaterFactory#onCreateView时,添加进mSkinViewList的View,作为需要换肤View的包装类,其中保存了需要换肤View的id。在调用mSkinLayoutInflater.getLayoutInflaterSkinViewList()时,会将进行findViewById将id对应的View也存进SkinView。跟进。

public class SkinView implements IChangeSkin {
    @Override
    public boolean findResource() {
        boolean changed = true;
        for (BaseSkinAttr attr : mSkinAttrList) {
            changed = attr.findResource();
            if (!changed) {
                break;
            }
        }

        return changed;
    }

    @Override
    public void changeSkin() {
        for (BaseSkinAttr attr : mSkinAttrList) {
            attr.applySkin(mView);
        }
    }
}

mSkinAttrList中保存了View所有需要换肤的属性。目前AndSkin只支持三种属性,分别是“background”、”src”和”TextColor”。BaseSkinAttr是个抽象类,三个子类分别为:BackgroundAttr、SrcAttr和TextColorAttr。这里以BackgroundAttr为例分析,其余两个同理。

public class BackgroundAttr extends BaseSkinAttr {

    public BackgroundAttr(String mAttrType, String mAttrName, String mAttrValueRef) {
        super(mAttrType, mAttrName, mAttrValueRef);
    }

    @Override
    public boolean findResource() {
        resetResourceValue();

        if (TYPE_ATTR_DRAWABLE.equals(mAttrType)) {
            mFindDrawable = ResourceManager.getDefault().getDataResource().getDrawableByName(mAttrValueRef);
            return mFindDrawable != null;

        } else if (TYPE_ATTR_COLOR.equals(mAttrType)) {
            mFindColor = ResourceManager.getDefault().getDataResource().getColorByName(mAttrValueRef);
            return mFindColor != Resource.VALUE_ERROR_COLOR;

        }
        return true;
    }

    @Override
    public void applySkin(View view) {
        if (TYPE_ATTR_DRAWABLE.equals(mAttrType) && mFindDrawable != null) {
            view.setBackgroundDrawable(mFindDrawable);
            SkinL.d(view + " : " + mAttrName + " apply " + mAttrValueRef);

        } else if (TYPE_ATTR_COLOR.equals(mAttrType) && mFindColor != Resource.VALUE_ERROR_COLOR) {
            view.setBackgroundColor(mFindColor);
            SkinL.d(view + " : " + mAttrName + " apply " + mAttrValueRef);
        }

        resetResourceValue();
    }
}

在findResource中通过ResourceManager.getDefault().getDataResource().getXXXByName获取到mFindDrawable或者mFindColor。在applySkin方法中设置给View即可。到此,我们已经完整的看到了整个轮廓,现在唯一差的是ResourceManager.getDefault().getDataResource().getXXXByName的实现细节,这也是换肤的真正精髓所在。

本地换肤使用的是LocalResource对象。

public class LocalResource extends Resource {

    public LocalResource(Context baseSkinActivity, String pluginPackageName, String pluginPath, String resourcesSuffix) {
        super(baseSkinActivity, pluginPackageName, pluginPath, resourcesSuffix);
        mResources = baseSkinActivity.getResources();
    }

    @Override
    public Drawable getDrawableByName(String drawableResName) {
        Drawable trueDrawable = null;
        drawableResName = appendSuffix(drawableResName);
        SkinL.d("getDrawableByName drawableResName:" + drawableResName);

        try {
            int trueDrawableId = mResources.getIdentifier(drawableResName, "drawable", GlobalManager.getDefault().getPackageName());
            trueDrawable = mResources.getDrawable(trueDrawableId);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return trueDrawable;
    }
}

public abstract class Resource {
    final String appendSuffix(String name) {
        if (!TextUtils.isEmpty(mResourcesSuffix)) {
            return name + "_" + mResourcesSuffix;
        }

        return name;
    }
}

前面说到:mResource.changeResourceSuffix(suffix)只是简单的记录night的后缀。现在就要用到这个night后缀了。在getDrawableByName中首先拼接名称,例如:将icon_search拼接为icon_search_night。之后通过mResources.getIdentifier获取到拼接后的资源的id。之后再转换成相应的Drawable。至此,本地换肤流程完毕。

动态换肤

动态换肤和本地换肤相比,换肤的流程是一样的,唯一的区别在于动态换肤的资源在另一个apk文件中。本地换肤是通过应用的Resources获取到相应的资源,那么动态换肤需要处理的问题就是怎么获取到外部apk的Resources对象。这个和插件化加载资源是一样的,构造外部apk的AssetManager即可。核心代码如下:

public class PluginResource extends Resource {

    public PluginResource(Context baseSkinActivity, String pluginPackageName, String pluginPath, String resourcesSuffix) throws Exception {
        super(baseSkinActivity, pluginPackageName, pluginPath, resourcesSuffix);
        loadPlugin();
    }

    private void loadPlugin() throws Exception {
        File file = new File(PATH_EXTERNAL_PLUGIN + "/" + mPluginPath);
        SkinL.d(file.getAbsolutePath());
        if (mPluginPath == null || !file.exists()) {
            throw new IllegalArgumentException("plugin skin not exit, please check");
        }

        AssetManager assetManager = null;

        assetManager = AssetManager.class.newInstance();
        Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
        addAssetPath.invoke(assetManager, file.getAbsolutePath());

        Resources superRes = mContext.getResources();
        mResources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
        SkinL.d("加载外部插件的皮肤成功");
    }
    ...
}

AndSkin优点

  1. 支持应用内换肤和动态换肤
  2. 不干预View的生成

AndSkin缺/槽点

由于在ConfigConstants写死了一堆常量。所以xml中的SKIN_NAMES_SPACE必须为“http://schemas.android.com/android/andSkin”。同理写死,状态栏的颜色值必须为status_bar_color。

为了不干预View的生成,存储了View的id,也就意味这同一个布局文件中需要换肤的View都设置id属性并且不能重复。假设需要在代码中动态inflate10个xml,那你要写10个相同的xml,只是View的id不同。这点特别坑爹。。。

src加载应该暴露个接口,让用户可以使用图片加载框架加载。。。

目前就发现这么多,欢迎吐槽。。

作者:qq_17250009 发表于2017/10/19 15:50:00 原文链接
阅读:0 评论:0 查看评论

安卓activity生存周期的onCreate、onRestoreInstanceState、onRestart、onStart、onResume、onPause、onStop、onDestroy

$
0
0

全栈工程师开发手册 (作者:栾鹏)

安卓教程全解

每一个Activity的状态是由它在Activity栈中所处的位置所决定的, Activity其是当前所有正在运行的Activity的后进先出的集合。 当一个新的Activity启动时 , 它就变为Activity 状态, 并被移动到栈顶 。 如果用户使用Back(返回)按钮返回到了刚才的Activity, 或者前台Activity被关闭了, 那么栈中的下一个Activity 就会移动到栈顶 ,变为活动状态。
这里写图片描述

这里写图片描述

这里进行以下操作演示窗口的生命周期:

1、启动activity1,点击activity1中的控件进入activity2,

2、点击手机home键,切换到手机主界面。

3、进入已经打开的activity2

4、点击返回键,关闭activity2,返回到activity1。

通过以上几步,了解窗口的onCreate、onRestoreInstanceState、onRestart、onStart、onResume、onSaveInstanceState、onPause、onStop、onDestroy各阶段的执行触发点。

activity2的窗口代码为

public class Activity2 extends Activity{  

    //完整生存期开始时调用
    @Override 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);   //调用基类方法
        setContentView(R.layout.activity2);  //setContentView可以通过扩展布局资源来对用户界面进行布局
        Log.v("生命周期", "完整生存期开始");
    }

    //在onCreate方法完成后调用,用户恢复UI状态
    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {
      super.onRestoreInstanceState(savedInstanceState);
      //从savedInstanceState恢复UI状态
      //这个bundle也被传递给onCreate.
      //自activity上次可见之后,只有当系统终止了该activity时,才会被调用
      Log.v("生命周期", "恢复UI状态");
    }

    //在随后的activity进程的可见生存期之前调用
    @Override
    public void onRestart(){
      super.onRestart();
      // 加装载改变,知道activity在此进程中已经可见
      Log.v("生命周期", "重启");
    }

    //在可见生存期(可见不一定聚焦)的开始时调用
    @Override
    public void onStart(){
      super.onStart();
      //既然activity可见,就应用任何要求的UI Change
      Log.v("生命周期", "启动");
    }

    //在activity状态生存期(前台聚焦期)开始时调用
    @Override
    public void onResume(){
      super.onResume();
      //恢复activity需要,但是当它处于不活动状态时被挂起的暂停的UI更新、线程或进程
      //在activity状态生命周期结束的时候调用,用来保存UI状态的改变
      Log.v("生命周期", "恢复");
    }

    // 把UI状态改变保存到savedInstanceState
    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
      //如果进程在运行时被终止或被重启,那么这个Bundle会保存UI状态,并将被传递给onCreate和onRestoreInstanceState用来恢复UI
        //onPause前会调用此函数
      super.onSaveInstanceState(savedInstanceState);
      Log.v("生命周期", "保存UI状态");
    }

    // 在activity状态生存期(前台聚焦期)结束时调用
    @Override
    public void onPause(){
      // 挂起不需要更新的UI更新、线程或者cpu密集的进程
      //当activity不是前台的活动状态的activity时
      super.onPause();
      Log.v("生命周期", "暂停");
    }

    //在可见生存期(可见不一定聚焦)结束时调用
    @Override
    public void onStop(){
      // 挂起不需要的UI更新、线程或处理(传感器监听器、GPS,定时器、service)
        //当activity不可见时,保存所有的编辑或者状态改变,因为在调用这个方法后,进程可能会被禁止
      super.onStop();
      Log.v("生命周期", "停止");
    }

    //在完整生存期结束时调用
    @Override
    public void onDestroy(){
      // 清理所有的资源,包括结束线程、关闭数据库连接等
      super.onDestroy();
      Log.v("生命周期", "销毁");
    }

}

activity1的窗口函数为

public class Activity1 extends Activity{  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);   
        setContentView(R.layout.activity1); 
        //点击控件,切换窗口
        TextView tv = (TextView)findViewById(R.id.activity1_text1);
        tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tosub();
            }
        });
    }

    //切换到窗口2
    public void tosub() {
        Intent myintent = new Intent();
        myintent.setClass(Activity1.this,Activity2.class);
        startActivity(myintent);  //启动新页面
    }
}

运行打印输出如下:

这里写图片描述

1、从activity1进入activity2,依次启动了onCreate、onStart、onResume函数

2、点击键盘后面键,使app进入后台,依次启动了onPause、onSaveInstanceState、onStop

3、从手机home界面重新进入已打卡的app,依次已启动了onRestart、onStart、onResume

在activity2窗口中点击物理返回键,关闭activity2,依次启动了onPause、onStop、onDestroy

作者:luanpeng825485697 发表于2017/10/17 20:45:41 原文链接
阅读:50 评论:0 查看评论

安卓application生命周期的onCreate、onLowMemory、onTrimMemory、onConfigurationChanged

$
0
0

全栈工程师开发手册 (作者:栾鹏)

安卓教程全解

安卓测试Application生命周期:

application类为应用程序的创建和终止、低可用内存和配置改变提供了事件处理程序(如前面部分所述)。
通过重写以下这些方法,可以为上述几种情况实现自己的应用程序行为:

• onCreate 在创建应用程序时调用。 可以重写这个方法来实例化应用程序单态,以及创建和实例化任何应用程序状态变量或共享资源。
• onLowMemory当系统处于资源匮乏的状态时,具有良好行为的应用程序可以释放额外的内存。这个方法一般只会在后台进程已经终止,但是前台应用程序仍然缺少内存时调用。可以重写这个处理程序来清空缓存或者释放不必要的资源。
• onTrimMemory 作为onlρwMemory的一个特定于应用程序的替代选择,在Android 4(API level 13)中引入。 当运行时决定当前应用程序应该尝试减少其内存开销时(通常在它进入后台时)调用。 它包含一个level参数,用于提供请求的上下文。
• onConfigurationChanged 与Activity不同,在配置改变时,应用程序对象不会被终止和重 启。 如果应用程序使用的值依赖于特定的配置,则重写这个方法来重新加载这些值,或者 在应用程序级别处理配置改变。

这里进行以下操作演示窗口的生命周期:

1、启动app应用

2、旋转手机屏幕

3、点击手机home键,切换到手机主界面。

通过以上几步,了解application的onCreate、onLowMemory、onTrimMemory、onConfigurationChanged各阶段的执行触发点。

首先要应用自己的Application类,需要在manifest文件中进行注册声明

<application
        android:allowBackup="true"
        android:name="com.lp.app.Application1"
        android:icon="@drawable/icon"
        android:label="@string/app_name"
        android:theme="@android:style/Theme.Light.NoTitleBar.Fullscreen"  >
       ...
</application>

自定义Application类代码

//扩展和使用Application类。
public class Application1 extends Application{  

    private static Application1 singleton;

    //返回应用程序实例
    public static Application1 getInstance(){
        return singleton;
    }

    //应用创建时调用,作为应用程序的初始化函数,创建新的状态变量和全局资源
    @Override
    public final void onCreate(){
        super.onCreate();
        singleton=this;
        Log.v("生命周期", "应用创建");
    }

    //系统资源匮乏时调用,在后台已终止,前台仍然缺少内存时调用
    @Override
    public final void onLowMemory(){
        super.onLowMemory();
        Log.v("生命周期", "系统资源匮乏,后台已终止,前台仍然缺少内存");
    }

    //系统资源匮乏时调用,应用程序进入后台时调用
    @Override
    public final void onTrimMemory(int level){
        super.onTrimMemory(level);
        Log.v("生命周期", "系统资源匮乏,应用程序进入后台");
    }

    //配置改变,与activity不同,应用程序不会重启
    @Override
    public final void onConfigurationChanged(Configuration newConfig){
        super.onConfigurationChanged(newConfig);
        Log.v("生命周期", "配置改变");
    }


}

运行打印输出如下:

这里写图片描述

1、启动app,会触发onCreate函数

2、屏幕旋转为横屏,触发onConfigurationChanged函数

3、屏幕旋转为竖屏,触发onConfigurationChanged函数

4、点击手机home进入手机主界面,触发onTrimMemory函数

作者:luanpeng825485697 发表于2017/10/17 21:19:57 原文链接
阅读:91 评论:0 查看评论

Android实现新闻效果原来如此简单

$
0
0

大家好,本篇文章教大家如何实现一个类似今日头条的效果。如果您熟悉RecyclerView的用法那么本篇文章会提供一个思路,如果您不会RecyclerView。本篇文章也会带你学习它。同时,在本文中您将会学会如何上拉加载,下拉刷新控件的用法。本文效果不仅仅限于新闻效果,同时比如购物列表,动态列表等效果都可以用本思路实现。效果图如下:


本文的知识点如下:

一:RecycerView+CommonPullToRefresh的实现上拉加载下拉刷新

二:每个item根据不同的类型加载不同的布局

三:解析JSON数据设置到每个item上


第一步:编写几种不同类型type的item布局

我们知道新闻列表有好几种item类型。本文只编写了2种布局,真正的新闻列表可能还包括视频布局,图集新闻等多种item类型。不过咱们以不变应万变,再多的item类型也不用担心。




单图文布局recyclerview_item_type_02.xml如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    >
    <LinearLayout
        android:layout_width="250dp"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >

        <TextView
            android:id="@+id/tx_news_simple_photos_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:maxLines="2"
            android:paddingBottom="5dp"
            android:paddingLeft="10dp"
            android:paddingTop="5dp"
            android:text="你没有看错!8天长假赴韩中国团体游客数量为0"
            android:textColor="#696969"
            android:textSize="18sp"
            android:textStyle="bold" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:paddingTop="10dp"
            android:paddingLeft="10dp"
            >
            <TextView
                android:id="@+id/tx_news_simple_photos_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="1小时前" />

            <TextView
                android:id="@+id/img_news_simple_photos_author"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="20dp"
                android:text="新闻集锦" />
        </LinearLayout>
    </LinearLayout>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        >
        <ImageView
            android:id="@+id/tx_news_simple_photos_01"
            android:layout_width="100dp"
            android:layout_height="80dp"
            android:layout_marginRight="10dp"
            android:src="@drawable/t01abe4bc24227b205b"
            />

    </RelativeLayout>
</LinearLayout>

效果如下:


多图文布局recyclerview_item_type_01.xml如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    >
    <TextView
        android:id="@+id/tx_news_mul_photos_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:paddingLeft="10dp"
        android:paddingTop="5dp"
        android:paddingBottom="5dp"
        android:text="你没有看错!8天长假赴韩中国团体游客数量为0"
        android:maxLines="2"
        android:textStyle="bold"
        android:textSize="18sp"
        android:textColor="#696969"
        />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:layout_gravity="center_horizontal"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/img_news_mul_photos_01"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:src="@drawable/t01abe4bc24227b205b" />

        <ImageView
            android:id="@+id/img_news_mul_photos_02"
            android:layout_width="wrap_content"
            android:layout_height="100dp"
            android:layout_weight="1"
            android:src="@drawable/am" />

        <ImageView
            android:id="@+id/img_news_mul_photos_03"
            android:layout_width="wrap_content"
            android:layout_height="100dp"
            android:layout_weight="1"
            android:src="@drawable/t01d931300dd6f99783" />
    </LinearLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingLeft="10dp"
        android:layout_marginTop="10dp"
        >
        <TextView
            android:id="@+id/tx_news_mul_photos_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="1小时前"
            />
        <TextView
            android:id="@+id/tx_news_mul_photos_author"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@id/tx_news_mul_photos_time"
            android:text="新闻集锦"
            android:layout_marginLeft="20dp"
            />
    </RelativeLayout>
</LinearLayout>

效果图如下:



第二步:添加依赖,导入RecyclerView和CommonPullToRefresh控件

 compile 'com.android.support:recyclerview-v7:24.0.0-alpha1'
 compile 'com.chanven.lib:cptr:1.1.0'


第三步:创建主布局,实例化RecyclerView控件并为其设置Adapter适配器

package com.example.mulrecyclerviewdemo;

import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.widget.Toast;

import com.chanven.lib.cptr.PtrClassicFrameLayout;
import com.chanven.lib.cptr.PtrDefaultHandler;
import com.chanven.lib.cptr.PtrFrameLayout;
import com.chanven.lib.cptr.loadmore.OnLoadMoreListener;
import com.chanven.lib.cptr.recyclerview.RecyclerAdapterWithHF;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private  String strJson="{\"pages\":1,\"data\":[{\"type\":0,\"title\":\"澳大利亚向非洲捐赠1000万美元\",\"time\":\"1小时前\",\"author\":\"卡尔网\",\"imgs\":[\"http://52sjw.com/images/jasdhisah.img\"]},{\"type\":1,\"title\":\"澳大利亚向非洲捐赠1000万美元\",\"time\":\"1小时前\",\"author\":\"卡尔网\",\"imgs\":[\"http://52sjw.com/images/jasdhisah.img\",\"http://52sjw.com/images/jasdhisah.img\",\"http://52sjw.com/images/jasdhisah.img\"]},{\"type\":0,\"title\":\"澳大利亚向非洲捐赠1000万美元\",\"time\":\"1小时前\",\"author\":\"卡尔网\",\"imgs\":[\"http://52sjw.com/images/jasdhisah.img\"]},{\"type\":1,\"title\":\"澳大利亚向非洲捐赠1000万美元\",\"time\":\"1小时前\",\"author\":\"卡尔网\",\"imgs\":[\"http://52sjw.com/images/jasdhisah.img\",\"http://52sjw.com/images/jasdhisah.img\",\"http://52sjw.com/images/jasdhisah.img\"]},{\"type\":0,\"title\":\"澳大利亚向非洲捐赠1000万美元\",\"time\":\"1小时前\",\"author\":\"卡尔网\",\"imgs\":[\"http://52sjw.com/images/jasdhisah.img\"]},{\"type\":1,\"title\":\"澳大利亚向非洲捐赠1000万美元\",\"time\":\"1小时前\",\"author\":\"卡尔网\",\"imgs\":[\"http://52sjw.com/images/jasdhisah.img\",\"http://52sjw.com/images/jasdhisah.img\",\"http://52sjw.com/images/jasdhisah.img\"]},{\"type\":0,\"title\":\"澳大利亚向非洲捐赠1000万美元\",\"time\":\"1小时前\",\"author\":\"卡尔网\",\"imgs\":[\"http://52sjw.com/images/jasdhisah.img\"]},{\"type\":1,\"title\":\"澳大利亚向非洲捐赠1000万美元\",\"time\":\"1小时前\",\"author\":\"卡尔网\",\"imgs\":[\"http://52sjw.com/images/jasdhisah.img\",\"http://52sjw.com/images/jasdhisah.img\",\"http://52sjw.com/images/jasdhisah.img\"]},{\"type\":0,\"title\":\"澳大利亚向非洲捐赠1000万美元\",\"time\":\"1小时前\",\"author\":\"卡尔网\",\"imgs\":[\"http://52sjw.com/images/jasdhisah.img\"]},{\"type\":1,\"title\":\"澳大利亚向非洲捐赠1000万美元\",\"time\":\"1小时前\",\"author\":\"卡尔网\",\"imgs\":[\"http://52sjw.com/images/jasdhisah.img\",\"http://52sjw.com/images/jasdhisah.img\",\"http://52sjw.com/images/jasdhisah.img\"]}]}";
    private RecyclerView recyclerView;
    private PtrClassicFrameLayout ptrClassicFrameLayout;
    private Handler handler=new Handler();
    private int page = 0;
    private List<NewsPhotoBean> list = new ArrayList<NewsPhotoBean>();
    private MulRecyclerViewAdapter adapter;
    private RecyclerAdapterWithHF mAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_acitivity);
        initView();
        adapter = new MulRecyclerViewAdapter(this,list);
        mAdapter = new RecyclerAdapterWithHF(adapter);
        recyclerView.setAdapter(mAdapter);
        ptrClassicFrameLayout.postDelayed(new Runnable() {

            @Override
            public void run() {
                ptrClassicFrameLayout.autoRefresh(true);
            }
        }, 150);
        ptrClassicFrameLayout.setPtrHandler(new PtrDefaultHandler() {
            @Override
            public void onRefreshBegin(PtrFrameLayout frame) {
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        page= 0;
                        list.clear();
                        try {
                            JSONObject jsonObject = new JSONObject(strJson);
                            JSONArray jsonArray  = jsonObject.getJSONArray("data");
                            for(int i = 0;i<10;i++){
                                NewsPhotoBean newsPhotoBean = new NewsPhotoBean();
                                newsPhotoBean.setType(jsonArray.getJSONObject(i).getInt("type"));
                                newsPhotoBean.setTitle(jsonArray.getJSONObject(i).getString("title"));
                                newsPhotoBean.setF_time(jsonArray.getJSONObject(i).getString("time"));
                                newsPhotoBean.setAuthor(jsonArray.getJSONObject(i).getString("author"));
                                int len = jsonArray.getJSONObject(i).getJSONArray("imgs").length();
                                List<String> ls = new ArrayList<>();
                                for(int j = 0;j<len;j++){
                                    String s = jsonArray.getJSONObject(i).getJSONArray("imgs").getString(j);
                                    ls.add(s);
                                }
                                newsPhotoBean.setList(ls);
                                list.add(newsPhotoBean);
                            }
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                        mAdapter.notifyDataSetChanged();
                        ptrClassicFrameLayout.refreshComplete();
                        ptrClassicFrameLayout.setLoadMoreEnable(true);
                        Log.d("TAG","正在刷新...");
                    }
                }, 1500);
            }
        });
        ptrClassicFrameLayout.setOnLoadMoreListener(new OnLoadMoreListener() {

            @Override
            public void loadMore() {
                handler.postDelayed(new Runnable() {

                    @Override
                    public void run() {
                        try {
                            JSONObject jsonObject = new JSONObject(strJson);
                            JSONArray jsonArray  = jsonObject.getJSONArray("data");
                            for(int i = 0;i<10;i++){
                                NewsPhotoBean newsPhotoBean = new NewsPhotoBean();
                                newsPhotoBean.setType(jsonArray.getJSONObject(i).getInt("type"));
                                newsPhotoBean.setTitle(jsonArray.getJSONObject(i).getString("title"));
                                newsPhotoBean.setF_time(jsonArray.getJSONObject(i).getString("time"));
                                newsPhotoBean.setAuthor(jsonArray.getJSONObject(i).getString("author"));
                                int len = jsonArray.getJSONObject(i).getJSONArray("imgs").length();
                                List<String> ls = new ArrayList<>();
                                for(int j = 0;j<len;j++){
                                    String s = jsonArray.getJSONObject(i).getJSONArray("imgs").getString(j);
                                    ls.add(s);
                                }
                                newsPhotoBean.setList(ls);
                                list.add(newsPhotoBean);
                            }
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                        mAdapter.notifyDataSetChanged();
                        ptrClassicFrameLayout.loadMoreComplete(true);
                        page++;
                        Log.e("TAG",page+"页");
                        Toast.makeText(MainActivity.this, "加载完成", Toast.LENGTH_SHORT).show();
                    }
                }, 1000);
            }
        });
    }



    private void initView() {
        ptrClassicFrameLayout = (PtrClassicFrameLayout) findViewById(R.id.test_list_view_frame);
        recyclerView = (RecyclerView) findViewById(R.id.news_recycler_view);
        LinearLayoutManager llm =new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false);
        recyclerView.setLayoutManager(llm);
        recyclerView.addItemDecoration(new RecyclerViewDivider(MainActivity.this, LinearLayoutManager.VERTICAL));

    }

}


第四步:创建RecyclerView的适配器。

package com.example.mulrecyclerviewdemo;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.widget.RecyclerView;
import android.util.Base64;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.List;

import static android.R.attr.author;
import static android.R.attr.type;
import static android.R.id.list;
import static android.media.CamcorderProfile.get;

/**
 * Created by chenlei on 2017/10/11.
 */

public class MulRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private static final int NEW_SIMPLE_TYPE = 0;//单图文模式
    private static final int NEW_MUL_TYPE = 1;//多图文模式
    private static final int NEW_OTHER_TYPE = 2;//多图文模式
    private Context context;
    private List<NewsPhotoBean> list;

    MulRecyclerViewAdapter(Context context, List<NewsPhotoBean> list) {
        this.context = context;
        this.list = list;
    }

    //重写getItemViewType方法,通过此方法来判断应该加载是哪种类型布局
    @Override
    public int getItemViewType(int position) {
        int type = list.get(position).getType();
            switch (type) {
                case 0:
                    return NEW_SIMPLE_TYPE;
                case 1:
                    return NEW_MUL_TYPE;
            }
        return NEW_OTHER_TYPE;
    }

    //根据不同的item类型来加载不同的viewholder
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(context);
        switch (viewType) {
            case NEW_SIMPLE_TYPE:
                return new NewsPhotoViewHolder(inflater.inflate(R.layout.recyclerview_item_type_02, parent, false));
            case NEW_MUL_TYPE:
                return new NewsPhotosViewHolder(inflater.inflate(R.layout.recyclerview_item_type_01, parent, false));
        }
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        //把对应位置的数据得到
            String title = list.get(position).getTitle();
            String time = list.get(position).getF_time();
            String author = list.get(position).getAuthor();
            List<String> ls = list.get(position).getList();//这里是json数据中的图片集合,也就是封面。不同类型item的封面图片数量是不一样的
      //  //无论是否单图文,标题和更新时间以及作者不变

        //如果单图文
        if (holder instanceof NewsPhotoViewHolder) {

            ((NewsPhotoViewHolder) holder).tx_news_simple_photos_title.setText(title);
            ((NewsPhotoViewHolder) holder).tx_news_simple_photos_time.setText(time);
            ((NewsPhotoViewHolder) holder).tx_news_simple_photos_author.setText(author);
//            ((NewsPhotoViewHolder) holder).img_news_simple_photos_01.setImageBitmap(btm_01);//单图文不用遍历直接将图片转换bitmap对象设置到ImageView上
            return;
        }
        //如果多图文
        if (holder instanceof NewsPhotosViewHolder) {
            ((NewsPhotosViewHolder) holder).tx_news_mul_photos_title.setText(title);
            ((NewsPhotosViewHolder) holder).tx_news_mul_photos_time.setText(time);
            ((NewsPhotosViewHolder) holder).tx_news_mul_photos_author.setText(author);
//            ((NewsPhotosViewHolder) holder).img_news_mul_photos_01.setImageBitmap(btm_01);//多图文需要遍历list将每个图片链接转换成Bitmap对象设置到ImageView上
//            ((NewsPhotosViewHolder) holder).img_news_mul_photos_02.setImageBitmap(btm_02);
//            ((NewsPhotosViewHolder) holder).img_news_mul_photos_03.setImageBitmap(btm_03);
            return;
        }
    }
    //具体item数据等于pages*10,每页10条
    @Override
    public int getItemCount() {

        return list.size();
    }

    /**
     * NewsPhotoViewHolder为单图文模式
     */
    class NewsPhotoViewHolder extends RecyclerView.ViewHolder {
        private TextView tx_news_simple_photos_title;//标题
        private ImageView img_news_simple_photos_01;//单图文模式的唯一一张图
        private TextView tx_news_simple_photos_time;//单图文模式的更新时间
        private TextView tx_news_simple_photos_author;//单图文模式的新闻作者

        public NewsPhotoViewHolder(View itemView) {
            super(itemView);
            tx_news_simple_photos_title = (TextView) itemView.findViewById(R.id.tx_news_simple_photos_title);//标题
//            img_news_simple_photos_01 = (ImageView) itemView.findViewById(R.id.tx_news_simple_photos_01);//单图文模式的唯一一张图
            tx_news_simple_photos_time = (TextView) itemView.findViewById(R.id.tx_news_simple_photos_time);//单图文模式的更新时间
            tx_news_simple_photos_author = (TextView) itemView.findViewById(R.id.img_news_simple_photos_author);//单图文模式的新闻作者

        }
    }

    /**
     * NewsPhotosViewHolder为多图模式
     */
    class NewsPhotosViewHolder extends RecyclerView.ViewHolder {
        private TextView tx_news_mul_photos_title;//标题
//        private ImageView img_news_mul_photos_01;//多图文模式的第一张图
//        private ImageView img_news_mul_photos_02;//多图文模式的第二张图
//        private ImageView img_news_mul_photos_03;//多图文模式的第三张图
        private TextView tx_news_mul_photos_time;//多图文模式的更新时间
        private TextView tx_news_mul_photos_author;//多图文模式的新闻作者

        public NewsPhotosViewHolder(View itemView) {
            super(itemView);
            tx_news_mul_photos_title = (TextView) itemView.findViewById(R.id.tx_news_mul_photos_title);
//            img_news_mul_photos_01 = (ImageView) itemView.findViewById(R.id.img_news_mul_photos_01);
//            img_news_mul_photos_02 = (ImageView) itemView.findViewById(R.id.img_news_mul_photos_02);
//            img_news_mul_photos_03 = (ImageView) itemView.findViewById(R.id.img_news_mul_photos_03);
            tx_news_mul_photos_time = (TextView) itemView.findViewById(R.id.tx_news_mul_photos_time);
            tx_news_mul_photos_author = (TextView) itemView.findViewById(R.id.tx_news_mul_photos_author);
        }
    }
}


第五步:封装每条item的数据到实体类中

NewsPhotoBean.java实体类代码:
package com.example.mulrecyclerviewdemo;

import android.graphics.Bitmap;
import android.widget.ImageView;

import java.util.List;

import static android.R.attr.type;

/**
 * Created by chenlei on 2017/10/11.
 */

public class NewsPhotoBean {

    public List<String> list;//封面图片的集合
    private int type;//排版类型
    private String title;//标题
    private String f_time;//发布时间
    private String author;//作者

    public List<String> getList() {
        return list;
    }

    public void setList(List<String> list) {
        this.list = list;
    }
    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }


    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }



    public String getF_time() {
        return f_time;
    }

    public void setF_time(String f_time) {
        this.f_time = f_time;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }


}

主布局main_acitivity.xml代码:
<?xml version="1.0" encoding="utf-8"?>
<com.chanven.lib.cptr.PtrClassicFrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:cube_ptr="http://schemas.android.com/apk/res-auto"
    android:id="@+id/test_list_view_frame"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#f0f0f0"
    cube_ptr:ptr_resistance="1.7"
    cube_ptr:ptr_ratio_of_header_height_to_refresh="1.2"
    cube_ptr:ptr_duration_to_close="200"
    cube_ptr:ptr_duration_to_close_header="1000"
    cube_ptr:ptr_keep_header_when_refresh="true"
    cube_ptr:ptr_pull_to_fresh="false">
    <android.support.v7.widget.RecyclerView
        android:id="@+id/news_recycler_view"
        android:layout_width="fill_parent"
        android:layout_height="match_parent"/>
</com.chanven.lib.cptr.PtrClassicFrameLayout>



给大家总结一下:
  1. 导入上拉加载跟多下拉刷新控件CommonPullToRefresh和RecyclerView控件。
  2. 编写item布局,有多少种item就编写多少种。
  3. 在下拉刷新上拉加载更多控件中嵌套RecyclerView控件
  4. 在主布局中实例化2个控件,并且重写CommonPullToRefresh的一些方法,具体的看该控件的用法,github项目中有用法说明,详情上拉加载更多下拉刷新控件的用法
  5. 编写RecyclerView适配器。
  6. 根据list传入的数据获取item的类型。
  7. 根据item类型编写不同的ViewHolder。
  8. 根据不同的ViewHolder进行数据和行为的绑定。


作者:qq_21004057 发表于2017/10/19 11:58:02 原文链接
阅读:28 评论:0 查看评论

iOS逆向工程之Hopper中的ARM指令

$
0
0

一、前言

iPhone用的ARM架构的处理器。如果你想对iOS系统以及你的应用进一步的了解,那么对ARM指令集的了解是必不可少的。

当你使用Hopper进行反编译时,里边全是ARM的指令
这篇博客就介绍一下ARM指令集的基础指令

Hopper的功能是非常强大的,在Hopper中你可以对ARM指令进行修改,并且生成一个新的可执行文件
Hopper会根据ARM汇编生成相关的逻辑图
红线表明条件不成立时的跳转,蓝线则表明条件成立时的跳转。
Hopper的功能强大到可以将ARM汇编生成相应的伪代码,如果你看ARM指令不直观的话,那么伪代码对你来说会更好一些

二、ARM指令集综述

ARM指令主要是对寄存器,栈、内存的操作。
寄存器位于CPU中,个数少速度快,ARM指令集中大部分指令都是对寄存器操作,但有些指令是对栈和内存的操作。

1.栈操作—- push 与pop

栈在ARM中所指的其实是一块具有栈数据结构特点内存区。
栈主要用来暂存寄存器中的值
对栈操作的命令就是push和pop

pushpop一般会成对出现,在函数开始时
将该函数执行时要使用的寄存器中的值push入栈,
然后在函数结束时将之前push到栈中的值在pop到相应的寄存器中。
0000c6ba         push       {r7, lr}                                            ; Objective C Block defined at 0x2eb24b0
0000c6bc         mov        r7, sp
0000c6be         movw       r0, #0xbab6                                         ; :lower16:(0x3388188 - 0xc6d2), &@selector(alloc), DATA XREF=0x2809324
0000c6c2         movt       r0, #0x337                                          ; :upper16:(0x3388188 - 0xc6d2), &@selector(alloc)
0000c6c6         movw       r2, #0xf950                                         ; :lower16:(0x33cc024 - 0xc6d4)
0000c6ca         movt       r2, #0x33b                                          ; :upper16:(0x33cc024 - 0xc6d4)
0000c6ce         add        r0, pc                                              ; &@selector(alloc)
0000c6d0         add        r2, pc                                              ; objc_cls_ref_MMLockMutableDictionary
0000c6d2         ldr        r1, [r0]                                            ; "alloc",@selector(alloc)
0000c6d4         ldr        r0, [r2]                                            ; objc_cls_ref_MMLockMutableDictionary,__objc_class_MMLockMutableDictionary_class
0000c6d6         blx        sub_e0b908
0000c6da         movw       r1, #0xbaaa                                         ; :lower16:(0x3388190 - 0xc6e6), &@selector(init)
0000c6de         movt       r1, #0x337                                          ; :upper16:(0x3388190 - 0xc6e6), &@selector(init)
0000c6e2         add        r1, pc                                              ; &@selector(init)
0000c6e4         ldr        r1, [r1]                                            ; "init",@selector(init)
0000c6e6         blx        sub_e0b908
0000c6ea         movw       r2, #0xe58e                                         ; :lower16:(0x348ac84 - 0xc6f6)
0000c6ee         movt       r2, #0x347                                          ; :upper16:(0x348ac84 - 0xc6f6)
0000c6f2         add        r2, pc                                              ; 0x348ac84
0000c6f4         ldr        r1, [r2]                                            ; 0x348ac84
0000c6f6         str        r0, [r2]                                            ; 0x348ac84, DATA XREF=0x2808520
0000c6f8         mov        r0, r1
0000c6fa         pop.w      {r7, lr}

2、 pc寄存器中的中的标志位

以32位指令为例,pc寄存器中的后四位是标志位,第28 - 31位分别对应着V (oVerflow),C (Carry),Z (Zero),N (Negative)。下面分别来介绍一下这四种符号所表示的状态。

N (Negative): 如果结果是负数则置位。
Z (Zero): 如果结果是零则置位。
C (Carry): 如果有进位则置位。
V (Overflow): 在发生溢出的时候置位。

3. 命令操作符

下方是ARM指令集中常用的算术操作:

(1)加法操作

ADD R0, R1, R2 ; R0 = R1 + R2
上面的命令就比较简单,就是讲两个数值进行相加。

ADC R0, R1, R2 ; R0 = R1 + R2 + C (Carry)
带进位的加法,ADC将把两个操作数加起来,并把结果放置到目的寄存器中。ADC使用了C–进位标志,这样就可以做比32位大的加法了。下方就是128位的数字进行加法操作的汇编代码。

例子:对一个128位的数字进行加法操作

因为我们使用的是32位的寄存器,所以要存储一个128位的数字,我们需要4个(128 / 32 = 4)寄存器。

所以我们假设R0,R1,R2,R3寄存器中分别由低到高存储着第一个数字,而R4, R5, R6, R7存储着第二个数字。
下方就是两个128数字相加操作的ARM汇编指令。

我们将结果存储在R8, R9, R10, R11这四个寄存器中。

首先我们执行的是将两个数的最低位相加并设置C标志位(ADDC R8, R0, R4),然后在进行下一位的操作,对R1和R5中的值进行相加,在相加后再加上上次操作的进位,然后再设置标志位,以此类推。这样我们最终的值就存储在了R8-R11这四个寄存器中。

(2)减法操作

SUB R0, R1, R2 ; R0 = R1 - R2
这个命名比较简单,就是使用R1寄存器中的值减去R2寄存器中的值,然后存储到R0中。

SBC R0, R1, R2       ; R0 = R1 - R2 - !C

```带借位的减法,假如我们当前的寄存器是32Bit, 如果两个64bit的数值进行减法操作就要使用到SBC借位操作。因为当两个数值在进行减法操作时,如果需要借位时就会把C标志位进行清零操作,所以在进行SBC操作时需要将C标志位进行取反操作。


`RSB R0, R1, R2       ; R0 = R2 - R1
`反向减法
`RSC R0, R1, R2       ; R0 = R2 - R1 - !C
`带借位的反向减法,上面这两个命令与SUBSBC命令差不多,都是进行减法操作的,不过操作数的计算顺序不同。





<div class="se-preview-section-delimiter"></div>

(3)、乘法指令
--------

在ARM指令集中,乘法指令有两种第一个是MUL, 第二个是带累加的乘法MLA。当然,这两个指令使用起来都不复杂。





<div class="se-preview-section-delimiter"></div>

MUL: 乘法指令 MUL{条件}{S} R0, R1, R2 ;R0 = R1 * R2
MLA: 乘法累加指令 MLA{条件}{S} R0, R1, R2, R3 ;R0 = R1 * R2 + R3






<div class="se-preview-section-delimiter"></div>

4)、逻辑操作
-------

与、或、非、异或





<div class="se-preview-section-delimiter"></div>

AND R0, R1, R2 ; R0 = R1 & R2
与操作, 1 & 1 = 1, 1 & 0 = 1, 0 & 1 = 1,0 & 0 = 0;






<div class="se-preview-section-delimiter"></div>

ORR R0, R1, R2 ; R0 = R1 | R2
或操作, 1 | 1 = 1, 1 | 0 = 1, 0 | 1 = 1, 0 | 0 = 0;






<div class="se-preview-section-delimiter"></div>

EOR R0, R1, R2 ; R0 = R1 ^ R2
异或,1 ^ 1 = 1, 1 ^ 0 = 0, 0 ^ 1 = 0, 0 ^ 0 = 1;






<div class="se-preview-section-delimiter"></div>

BIC R0, R1, R2 ; R0 = R1 &~ R2
位清除指令,现将R2进行取反,然后再与R1进行与操作。R1 & (~R2)
将R0的后四位清零:BIC R0, R0,#0x0F






<div class="se-preview-section-delimiter"></div>

MOV R0, R1 ;R0 = R1
赋值操作,将R1的值赋给R0






<div class="se-preview-section-delimiter"></div>

MVN R0, R1 ;R0 = ~R1
按位取反操作,将R1的每一位进行取反操作,然后赋值给R0
“`

4、寄存器的装载和存储

5、比较、分支与条件指令

6. 移位操作(LSL、ASL、LSR、ASR、ROR、RRX)

6. 移位操作(LSL、ASL、LSR、ASR、ROR、RRX)

作者:u011018979 发表于2017/10/18 18:52:19 原文链接
阅读:39 评论:0 查看评论

iOS逆向工程之Reveal工具的安装、配置与使用

$
0
0

前言

使用Hopper自己去破解官方的Reveal,网上有使用Hopper来修改Reveal汇编,破解Reveal 已经不适用最新版本

选中这一行,选择菜单栏的Modify > Assemble Instruction...,将jne修改成je,然后点击Assemble and Go Next

一、在越狱设备上配置Reveal

1. Reveal Loader安装

首先我们打款越狱设备的Cydia,然后在搜索中输入Reveal Loader,并且进行安装即可,下方是安装后的效果。这一步比较简单,安装后重启SpringBoard即可。

iPhone:~ root# cd /Library/RHRevealLoader
-sh: cd: /Library/RHRevealLoader: No such file or directory

进行第二步骤,导入libReveal.dylib

2.导入libReveal.dylib

libReveal.dylib

我们Mac上的Reveal自带了两个库,一个是libReveal.dylib,一个是Reveal.framework。
在未越狱的设备上使用库是后者,本篇博客中使用的是前者。
这两个文件位于Reveal中的iOS Library中。Reveal菜单->Help->Show Reveal Library in Finder ->iOS Library。通过上述目录就可以找到我们需要的文件。

 /Users/devzkn/Downloads/kevin-software/ios-Reverse_Engineering/Reveal/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib

mkdir

devzkndeMacBook-Pro:python-client devzkn$ ssh iphone
iPhone:~ root# mkdir /Library/RHRevealLoader
iPhone:~ root# cd /Library/RHRevealLoader
iPhone:/Library/RHRevealLoader root

scp

devzkndeMacBook-Pro:python-client devzkn$ scp  /Users/devzkn/Downloads/kevin-software/ios-Reverse_Engineering/Reveal/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib iphone:/Library/RHRevealLoader/
iPhone:~ root# ls -l /Library/RHRevealLoader/libReveal.dylib
-rwxr-xr-x 1 root admin 3850232 Oct 19 16:17 /Library/RHRevealLoader/libReveal.dylib

二、Reveal的使用

1.在设备上选择可以Reveal的App

在设置中找到Reveal的配置项,在该配置项中我们可以去选择要Reveal的App, 当然对于越狱手机,手机上安装的所有App都可以Reveal。当然也包括从AppStore下载的,也包括iOS系统自带的

比较类似于AFlexLoader

2.查看app的UI层级

Mac上Reveal查看设备上App的UI层级时是不需要使用USB进行连接的,但要保证你的iOS设备与你的Mac在同一个局域网内

注意事项

记得打开对应的app,保证你查看的app 处于运行状态

作者:u011018979 发表于2017/10/19 13:57:29 原文链接
阅读:21 评论:0 查看评论

Unity - MorphAnimation 超强变形动画编辑器(一) 蒙皮与变形

$
0
0

前言

忽略这个哗众取宠的标题,首先,为什么会有MorphAnimation,可能是我总觉得变形动画很有前途,因为看到一堆静态的东西我老是有一种想让他动一动的冲动,而骨骼蒙皮动画的前期准备工作又过于复杂了,起初我在Max里面蒙皮时,配顶点的权重信息搞得我晕头转向,所以我才想有一种很简单的能够让一个东西随心所欲的动起来的方法,至少能够跳过绑定骨骼和蒙皮的操作,然后我才发现,如果跳过了这两个步骤,那一切可能变得更加复杂,并不是Max做得不够好,而是某些东西本身就该这么复杂,所以我写了MorphAnimation,好吧,没看懂说了什么,这段独白权当做我发自内心深处的吐槽吧。

MorphAnimation的使用(一) 创建变形网格

首先,需要设置骨骼模式为四骨骼模式(Edit ->Project Setting -> Quality -> Other -> Blend Weights -> 4 Bones)。
然后导入MorphAnimation后,可以在任意静态模型的MeshFilter组件下点击Generate Morph Animation按钮,将模型网格转变为变形动画网格,转变后的网格会自动保存到工程Asset目录,记得不要删掉。
这里写图片描述

MorphAnimation的使用(二) 基本设置

转换成功后,GameObject会自动赋予MorphAnimation脚本,这是我们主要的编辑器,界面如下:

这里写图片描述

1、RenderSetting:渲染设置,继承至MeshRender的一些渲染设置。
2、MorphSetting:变形设置。
3、EditType:编辑模式,
Bone(骨骼层级):此模式下可以创建、修改、删除骨骼,并可以编辑骨骼的外封套、内封套,在骨骼层级编辑蒙皮信息。
Vertex(顶点层级):此模式下可以逐顶点查看或编辑每个顶点的蒙皮信息,比如更改绑定骨骼或重设权重值。
4、Apply按钮:全局应用,应用所有骨骼和顶点的变动信息,有新的改动之后最好点击一下此按钮。
5、3D Icon大小,
Vertex Size:在场景中,顶点图标的大小。
Bone Size:在场景中,骨骼图标的大小。
6、Done & Open In Editor Window按钮:结束骨骼绑定和蒙皮的操作,并打开至动画编辑界面,注意,结束之后,将无法再更改骨骼和蒙皮信息。

MorphAnimation的使用(三) 编辑骨骼

进入骨骼模式,可以创建、修改、删除骨骼,界面如下,这里我们以一个士兵的模型做讲解:
这里写图片描述

1、当前选中的骨骼,显示为明黄色,同时在编辑器中会以3D图标突出显示。
2、收缩与展开按钮:点击可以在编辑器界面收起或展开根骨骼。
3、Add按钮:添加无父骨骼的根骨骼。
4、AddSub按钮:(选中骨骼时)添加子骨骼至当前选中的骨骼。

注意:根骨骼之间无任何约束,可随意变形,但子骨骼会受到他直属父骨骼的约束:子骨骼与父骨骼之间无法被拉伸和压缩,只能做环绕运动,如果要创建随意变形动画,那就不要有子骨骼。

5、ReName按钮:(选中骨骼时)重命名当前选中的骨骼。
6、Delete按钮:(选中骨骼时)删除当前选中的骨骼,拥有子骨骼的骨骼无法删除,需要先删除子骨骼。
7、External Range骨骼外封套范围:在编辑器中表现为黄色外框,所有在此框内(同时在内封套外)的顶点将受到此骨骼的绑定,权重值随外封套的范围,越靠近边缘越小,但骨骼并未对该顶点拥有完全控制权,如果该顶点的绑定骨骼已达上限(4条),将不会接收到此骨骼的绑定影响。
8、Internal Range骨骼内封套范围:在编辑器中表现为青色外框,所有在此框内的顶点将受到此骨骼的绑定,骨骼将拥有对该顶点的完全控制权,权重值都为1,如果该顶点已有绑定骨骼,将会全部被此骨骼替换。
9、Apply Range按钮:应用封套范围,开始计算封套所影响的顶点。

注意:在骨骼编辑完成前,我们最好先不要应用封套,否则对于骨骼的移动、旋转、或缩放操作将会同时带动顶点。

然后我们继续添加骨骼,添加手臂:
这里写图片描述

添加臀部:

这里写图片描述

添加腿部:
这里写图片描述

添加脚部:
这里写图片描述

添加头部:
这里写图片描述

MorphAnimation的使用(四) 应用骨骼封套

我们调好骨骼的封套大小之后,点击Apply Range按钮应用封套,所有在骨骼封套范围内的顶点将会受到绑定影响,比如这里,我设置手部骨骼:
这里写图片描述

应用封套之后:
黄色的顶点表示受到外封套影响的顶点,该顶点将至少拥有一条绑定骨骼为此骨骼;
青色的顶点表示受到内封套影响的顶点,该顶点将完全绑定此骨骼,如果该顶点之前拥有绑定信息,这里都将会全部丢失,受到此骨骼的完全控制。
红色的顶点表示已经绑定了四条骨骼,不能再受到外封套的影响,可以进入顶点层级手动删掉他的绑定骨骼,重新应用封套。

MorphAnimation的使用(五) 编辑顶点权重

进入顶点模式,我们可以用鼠标左键单击选中模型身上的任意顶点,重新设置他的骨骼和权重信息,weight权重值越大,该顶点受到该骨骼的影响越大。

注意:顶点的四条骨骼的权重值的和必须等于1。

界面如下,被选中的顶点在编辑器中显示为青色3D图标,如图,我们当前选中的这个顶点由于在骨骼“左手”的内封套内,所以其完全受到“左手”的绑定,四条骨骼都为“左手”:
这里写图片描述

注意:进行这些操作之后都请点击Apply按钮,全部应用。

然后最麻烦的就是逐顶点校验骨骼和权重值,当然如果你只是需要一个简易模型的简单变形动画,这种细节的蒙皮操作你就可以直接跳过了,确保你的骨骼能够正常控制顶点就可以进入下一步了。

MorphAnimation的使用(六) 编辑动画

确定我们的骨骼和蒙皮信息无误以后,我们点击Done按钮,打开至动画编辑界面,界面如下:
这里写图片描述

1、预览动画按钮:点击之后可以对当前时间线上的关键帧进行播放预览。
2、Save Clip按钮:将当前时间线上的关键帧保存为动画剪辑文件。
3、Time Line时间线:当前时间线上的关键帧,只有处于时间线上的关键帧才能播放预览,或导出到剪辑。
4、添加按钮:从关键帧面板添加新的已有关键帧至时间线。
5、关键帧面板:显示未加入到时间线的关键帧。
6、Add Key Frame按钮:添加新的关键帧到关键帧面板。
7、动画的骨骼运动限制:
position:骨骼位置移动,开启之后骨骼的位置移动将会被记录到关键帧;
rotation:骨骼原地旋转,开启之后骨骼的原地旋转将会被记录到关键帧;
scale:骨骼原地缩放,开启之后骨骼的原地缩放将会被记录到关键帧;

我们选中关键帧面板的任意关键帧(显示为青色背景):
这里写图片描述

左侧为该关键帧的属性面板:
1、Bone List骨骼列表:该关键帧的骨骼列表(同时也是整个模型的骨骼列表),点击可选中某一骨骼(显示为红色背景),同时在场景中该骨骼被选中,可以执行移动、缩放、旋转等操作。

注意:这些操作将改变关键帧数据,将模型调节到新的形态后,记得点击Apply Bone按钮,应用所有骨骼数据到当前选中的关键帧,否则切换到其他关键帧后,你的改动会丢失。

2、Frame Name:关键帧全名。
3、Frame Time:关键帧时间,加入到时间线后,此关键帧的运行时间,1表示1秒,也就是说在播放动画时需要1秒钟才播放完此关键帧(同时会受到动画全局速度的影响),此值越大,动画在达到此关键帧时播放得越慢。
4、Apply Bone按钮:应用所有骨骼数据到当前选中的关键帧。
5、ReName按钮:(选中关键帧面板的关键帧时)重命名当前选中的关键帧。
6、Delete按钮:(选中关键帧面板的关键帧时)删除当前选中的关键帧。

点击时间线后面的加号按钮,我们将关键帧面板的任意关键帧加入到时间线,然后我们选中时间线上的任意关键帧(显示为青色背景):
这里写图片描述

1、Leave按钮:离开时间线,让当前选中的关键帧离开时间线并回到关键帧面板。
2、Insert按钮:插入关键帧,在当前位置插入关键帧,同时当前选中的关键帧及之后的关键帧位置后移。
3、Replace按钮:替换关键帧,将当前选中的关键帧替换为关键帧面板的其他关键帧。

MorphAnimation的使用(七) 预览动画

确保时间线上至少拥有两个关键帧后,我们点击预览动画按钮:
这里写图片描述

预览动画的按钮为红色表示动画在播放中,再次点击可结束预览。

MorphAnimation的使用(八) 导出动画剪辑

点击Save Clip按钮,将当前时间线上的所有关键帧导出为一个动画剪辑资源文件,之后这个资源文件便可以被MorphAnimationPlayer引用。

MorphAnimation的使用(九) 播放动画

我们为MorphAnimation所在的物体添加MorphAnimationPlayer脚本,将刚才保存的动画剪辑拖拽到Clip属性,勾选Play On Awake,运行场景便可以看到动画效果。

这里写图片描述

注意:刚才导出的动画剪辑只能用于之前编辑动画时的MorphAnimation所在的物体,如果需要第二个物体也能使用,克隆这个物体就可以了,当然,MorphAnimation脚本可以删除,只不过留在这里可以用于以后制作新的动画剪辑。

相对来说,制作一些简单的变形动画还是很可观的,要非常细致的蒙皮的话,那注定会是非常麻烦的,总之我这里为那个士兵模型的蒙皮最后就被我放弃了,因为实在是太繁琐了。

github源码链接:https://github.com/SaiTingHu/Morph-Animation

一起学习和进步

——by MeshEditor

作者:qq992817263 发表于2017/10/19 19:16:00 原文链接
阅读:43 评论:0 查看评论

[Android]自己动手做个拼图游戏

$
0
0

目标

在做这个游戏之前,我们先定一些小目标列出来,一个一个的解决,这样,一个小游戏就不知不觉的完成啦。我们的目标如下:
1. 游戏全屏,将图片拉伸成屏幕大小,并将其切成若干块。
2. 将拼图块随机打乱,并保证其能有解。
3. 在屏幕上留出一个空白块,当点空白块旁边的块,将这块移动到空白块。
4. 判断是否已经拼好。
截图

实现目标

1.将图片拉伸成屏幕大小,并将其切成若干块。

想拉伸成屏幕大小,首先要知道屏幕的大小,Android获得屏幕大小的代码如下:

DisplayMetrics metrics =new DisplayMetrics();
getWindowManager().getDefaultDisplay().getRealMetrics(metrics);//sdk17+
int screenWidth = metrics.widthPixels;//屏幕宽
int screenHeight = metrics.heightPixels;//屏幕高

将图片拉伸到屏幕大小

Bitmap back=Bitmap.createScaledBitmap(bitmap,
MainActivity.getScreenWidth(),
MainActivity.getScreenHeight(),
true);

将图片切成若干块

 private final int COL=3;//行,默认3行
 private final int ROW=3;//列,默认3列
 int tileWidth=back.getWidth()/COL;//每一块的宽
 int tileHeight=back.getHeight()/ROW;//每一块的高
 Bitmap[] bitmapTiles =new Bitmap[COL*ROW];
 int idx=0;
 for(int i=0;i<ROW;i++)
 {
     for(int j=0;j<COL;j++)
     {
         bitmapTiles[idx++]=Bitmap.createBitmap(back,
         j*tileWidth,
        tileWidth,tileHeight);
     }
 }

2. 将拼图块随机打乱,并保证其能有解。

这个问题应该是这个小游戏的核心了,有些人在做拼图的时候就随便乱摆,最后发现拼不回来,超级尴尬。要想打乱了还能拼回来,我们呢,就想到了模拟人打乱拼图的方法,就是将空白块与旁边的非空白块交换位置,与旁边哪个非空白块交换是随机的,然后将这个过程重复若干次,重复的次数也是随机的,这样一来,保证了图块的随机,又保证了能拼回来。在这里我们用数字0到N-1(N为块的数量)表示每一块,并用二维数组存储他们。

    private void createIntegerArray(int row,int col)
    {
        array=new int[row][col];
        int idx=0;
        for(int i=0;i<row;i++)
        for(int j=0;j<col;j++)
            array[i][j]=idx++;
    }

下面是打乱块的算法,最后一块是空白块,让它随机与旁边的某一块进行交换,这个过程中要检查数组边界,不要让它越界。

    //四个方向
   private int[][] dir={
        {0,1},//下
        {1,0},//右
        {0,-1},//上
        {-1,0}//左
    };
    /**
     * 移动块的位置
     * @param srcX 初始x位置
     * @param srcY 初始y位置
     * @param xOffset x偏移量
     * @param yOffset y偏移量
     * @return 新的位置,错误返回new Point(-1,-1);
     */
    private Point move(int srcX,int srcY,int xOffset,int yOffset)
    {
        int x=srcX+xOffset;
        int y=srcY+yOffset;
        if(x<0||y<0||x>=col||y>=row)
            return new Point(-1,-1);

        int temp=array[y][x];
        array[y][x]=array[srcY][srcX];
        array[srcY][srcX]=temp;

        return new Point(x,y);
    }

    /**
     * 得到下一个可以移动的位置
     * @param src 初始的点
     * @return
     */
    private  Point getNextPoint(Point src)
    {
        Random rd=new Random();
        int idx=rd.nextInt(4);//,因为有4个方向,所以产生0~3的随机数
        int xOffset=dir[idx][0];
        int yOffset=dir[idx][1];
        Point newPoint=move(src.getX(),src.getY(),xOffset,yOffset);
        if(newPoint.getX()!=-1&&newPoint.getY()!=-1) {
            return newPoint;//找到了新的点
        }

       return getNextPoint(src);//没有找到,继续
    }

    /**
     * 生成拼图数据
     * @param row
     * @param col
     * @return
     */
    public int[][] createRandomBoard(int row,int col)
    {
        if(row<2||col<2)
            throw new IllegalArgumentException("行和列都不能小于2");
        this.row=row;
        this.col=col;
        createIntegerArray(row,col);//初始化拼图数据
        int count=0;
        Point tempPoint=new Point(col-1,row-1);//最后一块是空白块
        Random rd=new Random();
        int num=rd.nextInt(100)+20;//产生20~119的随机数,表示重复的次数
        while (count<num)
       {
           tempPoint=getNextPoint(tempPoint);//获得下个点,并更新空白块位置
           count++;
        }
        return  array;
    }

3. 在屏幕上留出一个空白块,当点空白块旁边的块,将这块移动到空白块。

留出空白块很简单,由于上面我们将最后一块作为空白块。当我们绘图时,略过它即可。代码实现如下:

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.GRAY);
        for(int i=0;i<ROW;i++) {
            for (int j = 0; j < COL; j++) {
                int idx=dataTiles[i][j];
                if(idx==ROW*COL-1&&!isSuccess)
                    continue;
                canvas.drawBitmap(bitmapTiles[idx],
                j*tileWidth,
                i*tileHeight,paint);
            }
        }
    }

移动块也很简单,当点击屏幕时,计算其在拼图数据中对应的索引。当计算到点击非空白块就寻找它旁边有没有空白块,有,则将拼图数据中表示空白块和非空白块的数据交换,并刷新View即可

    /**
     * 将屏幕上的点转换成,对应拼图块的索引
     * @param x
     * @param y
     * @return
     */
    private Point xyToIndex(int x,int y)
    {
        int extraX=x%tileWidth>0?1:0;
        int extraY=x%tileWidth>0?1:0;
        int col=x/tileWidth+extraX;
        int row=y/tileHeight+extraY;

        return new Point(col-1,row-1);
    }
    /**
    *点击屏幕时发生
    */
     @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(event.getAction()==MotionEvent.ACTION_DOWN) {
            Point point = xyToIndex((int) event.getX()
            , (int) event.getY());

            for(int i=0;i<dir.length;i++)
            {
                int newX=point.getX()+dir[i][0];
                int newY=point.getY()+dir[i][1];

                if(newX>=0&&newX<COL&&newY>=0&&newY<ROW){
                    if(dataTiles[newY][newX]==COL*ROW-1)
                    {
                        int temp=dataTiles[point.getY()][point.getX()];
                        dataTiles[point.getY()][point.getX()]=dataTiles[newY][newX];
                        dataTiles[newY][newX]=temp;
                        invalidate();
                    }
                }
            }
        }
        return true;
    }

4. 判断是否已经拼好

我们初始化数据时,是从0开始,依次增加作为拼图数据。当拼好的时候,拼图数据也应该是一样的,所以我们将拼图二维数组的数据复制到一维数组中,然后比较数组中每一个数据与它的下一个数据,如果都小于,说明数组里面的数据已经从小到大排列好。

    /**
     * 通过求逆序数判断是否拼图成功,逆序数为0,拼图成功
     * @param arr
     * @return
     */
    public boolean isSuccess(int[][] arr)
    {
        int sum=0;
        int idx=0;
        int[] newArr=new int[row*col];

        for (int i=0;i<arr.length;i++) {
            for (int j=0;j<arr[i].length;j++) {
                newArr[idx++]=arr[i][j];
            }
        }
        for(int i=0;i<newArr.length;i++)
        {
            for(int j=i+1;j<newArr.length;j++)
            {
                if(newArr[j]<newArr[i])
                {
                    sum++;
                }
            }

        }
        return  sum==0;
    }

拼图游戏技巧

拼图技巧觉得也有必要教一下,不然有些人就会说:你的算法有问题,这根本拼不好!我也是超级无奈啊!拼图的技巧是,我们先把上面的第一行拼好,然后再把第二行拼好,这样,一直下去~就能完全拼好了。

总结

这个小游戏简单,可以拿来练手,还可以拿来装(liao)逼(mei),如果不会,何乐而不看呢。这个小游戏也是将视图和数据分开,代码容易移植。

项目地址

https://github.com/luoyesiqiu/PuzzleGame

作者:e_one 发表于2017/10/20 12:49:17 原文链接
阅读:271 评论:0 查看评论

NSString 使用 copy 关键字和 strong 关键字修饰的异同

$
0
0

NSString 为什么要用 copy 关键字,如果用 strong 会有什么问题?当然,这里没有说用 strong 就一定不行,使用 copy 和 strong 是看情况而定的。网上也有很多文章是解释这一点的,但都不够形象清晰,博主的博客风格是力求简单易懂,尽量用实例来说明问题。

不说太多废话,用最简单的例子来说明问题,直接上代码和运行截图:
这里写图片描述

通过上面的例子可以看出,当使用 strong 修饰 NSString 时,我们在没有直接修改 self.name 的情况下 self.name 却被修改了,就好像一个人的名字没有经过同意就被修改了,我们的初衷只是单纯地修改 str1,但是 self.name 却被意外修改了,而这就是使用 strong 所不想看到的结果,会破坏程序的封装性。

说明:使用 strong 后 self.name 和 str1 指向的是同一片内存区域,所以修改其中一个值后两个值就都变了。

而当使用 copy 修饰 NSString 时,str2 通过 copy 得到了一个新的对象赋值给 self.name,这样我们再修改 str2 的值就跟 self.name 没关系了,只有直接对 self.name 进行赋值才能改变它的值,这样也就保证了程序的封装性。

本文只是对NSString 使用 copy 关键字和 strong 关键字修饰的异同做一个简单的总结和分享,更深入了解还需学习浅拷贝与深拷贝相关知识,后续博文会陆续更新!

作者:huangfei711 发表于2017/10/20 17:40:05 原文链接
阅读:183 评论:0 查看评论

143. Reorder List。

$
0
0

Given a singly linked list L: L0→L1→…→Ln-1→Ln,
reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→…

You must do this in-place without altering the nodes’ values.

For example,
Given {1,2,3,4}, reorder it to {1,4,2,3}.


题中让链表重新整理,按照L0->Ln->L1->L(n-1)。。。的顺序进行,这道题相对来说还是比较简单的。只需要将链表从中间分开,将原来的一条链表分成两节。将第二节进行反转之后再次与第一节交叉链接即可。比如说原来的1->2->3-4,从中间分开之后就是1->2,3->4,然后将第二节反转:4->3,两节按照:第一节取出一个节点,第二节取出一个节点的顺序交叉链接:1->4->2->3。

#include <iostream>
#include <unordered_set>
#include <set>
#include <algorithm>
#include <vector>

using namespace std;

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

class Solution {
public:
    void reorderList(ListNode* head) {
        if(!head || !head->next) {
            return;
        }

        ListNode *p1 = head;
        ListNode *p2 = head->next;

        //利用快慢指针的性质将链表平均分开,偶数的话平均分,奇数的话第一节比第二节多一个
        while(p2&&p2->next) {
            p1 = p1->next;
            p2 = p2->next->next;
        }

        ListNode* head2  = p1->next;//p1指向第一节的最后一个节点,所以下一个就是第二节的开始
        ListNode* temp;
        p1->next = NULL;//彻底断开

        p2 = head2->next;//指向head2的下一个节点

        head2->next = NULL;//将此时head2孤立开,为了做第二节反向后的最后一个节点

        while(p2) {
            temp = p2->next;//先用temp记录下p2的下一个节点
            p2->next = head2;//反向链接
            head2 = p2;
            p2 = temp;
        }

        /*while(head) {
            cout << head->val << ",";
            head = head->next;
        }

        cout << endl;

        while(head2) {
            cout << head2->val << ",";
            head2 = head2->next;
        }*/

        //将两节链表融合起来
        p1 = head;
        p2 = head2;

        //交叉链接起来
        while(p2) {//要么两节相等,要么第一节多,所以这里用第二节做为判断空的条件
            temp = p1->next;
            p1->next = p2;
            p1 = p2;
            p2 = temp;
        }

        /*while(head) {
            cout << head->val << ",";
            head = head->next;
        }*/

    }
};

int main() {
    Solution s;

    ListNode node1(1);
    ListNode node2(2);
    ListNode node3(3);
    ListNode node4(4);
    ListNode node5(5);
    ListNode node6(6);

    node1.next = &node2;
    node2.next = &node3;
    node3.next = &node4;
    node4.next = &node5;
    node5.next = &node6;

    s.reorderList(&node1);

}

作者:Leafage_M 发表于2017/10/20 19:20:15 原文链接
阅读:177 评论:0 查看评论

安卓获取res下的资源文件:string字符串、color颜色、dimen尺寸、array数组、drawable图片和xml、anim/animator动画、raw媒体、assets资源

$
0
0

全栈工程师开发手册 (作者:栾鹏)

安卓教程全解

安卓获取内部资源并应用。

1、获取res/values文件夹下的string.xml的字符串、color.xml的颜色、dimen.xml的尺寸、array.xml中的字符串数组,array.xml中的整型数组。

public void getresource() {
    Resources myResources = getResources();   //获取资源表实例

    CharSequence mytext = myResources.getText(R.string.str1);   //获取字符串,string.xml
    textView1.setText(mytext);  

    int mycolor= myResources.getColor(R.color.red);  //获取颜色,color.xml
    textView1.setTextColor(mycolor);    

    float mydimen=myResources.getDimension(R.dimen.dimen2);  //获取尺寸,dimen.xml
    textView1.setTextSize(mydimen);     

    String[] mystrarray=myResources.getStringArray(R.array.str_array);  //获取字符串数组,array.xml
    Log.v("资源字符串数组", mystrarray.toString());

    int[] myintarray=myResources.getIntArray(R.array.int_array);  //获取整型数组,array.xml
    Log.v("资源整型数组", myintarray.toString());

}

2、获取系统字符串、系统动画函数

//系统资源的使用
public void get_system_resource() {
    CharSequence mytext = getString(android.R.string.httpErrorBadUrl);   //获取字符串
    Log.v("系统字符串", mytext.toString());      
}

3、获取drawable文件夹下的图片和xml

 public void getpic() 
 {
    Resources myResources = getResources();         
    Drawable myDrawable = myResources.getDrawable(R.drawable.img1);   //获取drawable文件夹下的图像,
    imageView2.setBackgroundDrawable(myDrawable);   //设置为背景

    imageView2.setBackgroundResource(R.drawable.img1);   //将上面两步合成一步


    Drawable myDrawable1 = myResources.getDrawable(R.drawable.img0);   //获取drawable文件夹下图像或xml
    imageView2.setImageDrawable(myDrawable1);   //设置为显示图片,在背景层之上

    imageView2.setImageResource(R.drawable.img0); //将上面两步合成一步
}

4、获取anim文件夹下的视图动画、获取animator文件夹下的属性动画、drawable文件夹下的逐帧动画。

public void getanim() 
{
    Animation myAnimation2=AnimationUtils.loadAnimation(this, R.anim.anim2);  //获取视图动画,anim文件夹下
    textView1.setAnimation(myAnimation2);   //应用视图动画,自动启动

    ObjectAnimator myAnimator1 = (ObjectAnimator)AnimatorInflater.loadAnimator(this, R.animator.anim1);  //获取属性动画,animator文件夹下
    myAnimator1.setTarget(button1);  //动画绑定控件
    myAnimator1.start();   //启动属性动画

    imageView1.setBackgroundResource(R.drawable.anim3);   //获取逐帧动画,drawable文件夹下,动画绑定控件
    AnimationDrawable myanimation3 = (AnimationDrawable) imageView1.getBackground();    //获取对动画的引用
    myanimation3.start();  //启动逐帧动画
}

5、获取raw文件夹下的静态媒体文件

public void getraw() 
{
        Resources myResources = getResources();   
        InputStream myfile=myResources.openRawResource(R.raw.music1);   //文件形式读取

        //音乐文件
        MediaPlayer mediaPlayer = MediaPlayer.create(this, R.raw.music1);   //创建音乐媒体
        mediaPlayer.start();  //启动音乐播放

        //视频文件(3gp,wmv,mp4),通过uri
        String uri = "android.resource://" + getPackageName() + "/" + R.raw.test;
        videoView1.setVideoURI(Uri.parse(uri));
        videoView1.start();
}

6、获取assets文件夹下的文件

public void getassets() {
        try {
            AssetManager assetManager = this.getAssets();
            String [] files = assetManager.list(""); //遍历assets根目录

            //图片文件
            InputStream is = assetManager.open("img1.jpg");   //获取文件流
            Bitmap image = BitmapFactory.decodeStream(is);   //将图片文件转化为图片
            is.close();
            //文本文件
            is = assetManager.open("test.txt");   //获取文件流
            int size = is.available();
            byte[] buffer = new byte[size];
            is.read(buffer);
            is.close();
            String text = new String(buffer, "utf-8");
            Log.v("assets资源", text); 
            //音乐文件
            MediaPlayer player = new MediaPlayer();
            AssetFileDescriptor fileDescriptor = assetManager.openFd("music1.mp3");
            player.setAudioStreamType(AudioManager.STREAM_MUSIC);  
                                                player.setDataSource(fileDescriptor.getFileDescriptor(),fileDescriptor.getStartOffset(),fileDescriptor.getLength());
            player.prepare();
            player.start();


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


    }
作者:luanpeng825485697 发表于2017/10/19 18:46:14 原文链接
阅读:74 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>