仿Big-Bang实现

前几天去看了锤子的发布会,没想到这次还是诚意满满。作为程序员,就关注了在本次发布会出现的两大创意(其实只能算得上微创新)。第一个就是本次要说的big-bang,另一个是One Step。

先简要介绍一下One Step,毕竟本次的主题不是他。在Android中其实已经有了相关的介绍,不知道各位基友注意到没有。早在API11的时候官方推出一个新的API—Drag and Drop。官方链接
无法科学上网的童鞋们只能通过下图(截取了整个界面)来看这个功能的实现。
图片下载,图片太大了
详情请看—链接大神的解析

下面来进入BigBang的学习过程,通过发布会上简简单单的演示,我们可以看到一共有三步,系统级拦截长触摸事件+系统剪切板+分词技术。这三个技术现在都比较成熟,不要担心。如果想实现系统级的拦截可以使用Xposed技术,用来拦截,在这里不做赘述。
现在主要分为两部分讲实现BigBang技术的实现。

1. 实现基本功能

本部分主要介绍系统剪切板,中文/英文分词的实现和问题的解决

1,使用系统剪切板
首先启动一个服务,在其他软件上使用剪切板时可以监控通知软件有新的内容出现,并在通知栏上进行提示用户操作。
ClipServer.java(其他方法暂不赘述)

     @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        final ClipboardManager clipboardManager = (ClipboardManager) getSystemService
                (CLIPBOARD_SERVICE);
        clipboardManager.addPrimaryClipChangedListener(() -> {
            if (clipboardManager.hasPrimaryClip()) {
                if (clipboardManager.getPrimaryClipDescription().hasMimeType(
                        ClipDescription.MIMETYPE_TEXT_PLAIN)) {
                    ClipData primaryClip = clipboardManager.getPrimaryClip();
                    if (primaryClip != null) {
                        ClipData.Item item = primaryClip.getItemAt(0);
                        if (item != null && !TextUtils.isEmpty(item.getText().toString())) {
                            startNofi(item.getText().toString());
                        }
                    }
                }
            }
        });
        return super.onStartCommand(intent, flags, startId);
    }
     private void startNofi(String s) {
        manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        Intent resultIntent = new Intent(this, ReceiverClip.class);
        resultIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        resultIntent.putExtra(ReceiverClip.EXTRA_TEXT, s);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, resultIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
        notifyBuilder = new NotificationCompat.Builder(this)
                .setLargeIcon(bitmap)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle(getResources().getText(R.string.nofi_tongzhi))
                .setContentText(getResources().getText(R.string.nofi_context))
                .setWhen(System.currentTimeMillis())
                .setTicker("Big bang")
                .setOngoing(true)
                .setAutoCancel(true)
                .setContentIntent(pendingIntent);
        manager.notify(121, notifyBuilder.build());
    }

在MainActivity上进行启动服务,在这里童鞋们可以添加一些(XX)的技术,尽量使这个服务线程保活。比如双进程守护(像NDK双进程守护),系统的辅助服务,相互唤醒(全家桶),悬浮窗1px等等(这样的技术在此不做描述,毕竟国内的环境,,,,)。

MainActivity.java

 private void initData() {
        Intent intent = new Intent(MainActivity.this, ClipServer.class);
        startService(intent);
    }

系统剪切板就是这点内容,下面是是分词技术。
2.中文/英文分词技术
通过百度/谷歌查询可知分词是比较成熟的,像Word分词器,IKAnalyzer分词器等等,使用Java实现的分词器(知名的)十多个。
在本文中主要使用的是IKAnalyzer+Lucene(本地)。做过功课的童鞋们大概也看出来分词器需要web端实现或添加大字典。本人猜测锤子的分词是在服务端实现的,毕竟本地实现分词不仅需要大字典还有运算时的高耗,在本地实现运算一段100字左右的段落,会占用大约2s-3s的时间(我的渣渣手机),同时还有IKAnalyzer.cfg.xml,ext_stopword.dic两个文件,作为字典,配置文件。
言归正传,我用的是IKAnalyzer-2012_u3(好像不更新了),和Lucene的三个包,lucene-analyzers-3.1.0.jar,lucene-core-3.6.0.jar,lucene-highlighter-3.6.0-sources.jar。在Maven仓库里面没有Lucene的三个包(有点老),大家在官网上下,或者在本文最后提供的地址中去爬。
使用jdk1.8的童鞋们在build.gradle中添加

 compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

在解析界面,使用代码:

String text = getIntent().getStringExtra(EXTRA_TEXT);
        if (!TextUtils.isEmpty(text)) {
            List<String> keywordList = new ArrayList<>();
            try {
                byte[] bt = text.getBytes();
                InputStream ip = new ByteArrayInputStream(bt);
                Reader read = new InputStreamReader(ip);
                IKSegmenter iks = new IKSegmenter(read, true);//true开启只能分词模式,如果不设置默认为false,也就是细粒度分割
                Lexeme t;
                while ((t = iks.next()) != null) {
                    keywordList.add(t.getLexemeText());
                    mLayout.addTextItem(t.getLexemeText());
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

就是这么短的代码量,是不是很惊喜,如此简单的实现。
好了,现在实现界面。
3,分词界面
主要就是一个流式布局,TouchEvent事件。在触摸一个词时加入StringBuilder中。下面放出一个大神写的仿锤子布局(本人实力不够,,,,)。
BigBangLayout.java

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

        int widthSize = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
        int contentWidthSize = widthSize - mActionBar.getContentPadding();
        int heightSize = 0;

        int childCount = getChildCount();

        int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);

        mLines = new ArrayList<>();
        Line currentLine = null;
        int currentLineWidth = contentWidthSize;
        for (int i = 0; i < childCount; i++) {

            View child = getChildAt(i);

            if (mActionBar == child) {
                continue;
            }

            child.measure(measureSpec, measureSpec);

            if (currentLineWidth > 0) {
                currentLineWidth += mItemSpace;
            }
            currentLineWidth += child.getMeasuredWidth();
            if (mLines.size() == 0 || currentLineWidth > contentWidthSize) {
                heightSize += child.getMeasuredHeight();
                currentLineWidth = child.getMeasuredWidth();
                currentLine = new Line(mLines.size());
                mLines.add(currentLine);
            }
            Item item = new Item(currentLine);
            item.view = child;
            item.index = i;
            item.width = child.getMeasuredWidth();
            item.height = child.getMeasuredHeight();
            currentLine.addItem(item);
        }

        Line firstSelectedLine = findFirstSelectedLine();
        Line lastSelectedLine = findLastSelectedLine();
        if (firstSelectedLine != null && lastSelectedLine != null) {
            int selectedLineHeight = (lastSelectedLine.index - firstSelectedLine.index + 1) * (firstSelectedLine.getHeight() + mLineSpace);
            mActionBar.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(selectedLineHeight, MeasureSpec.UNSPECIFIED));
        }

        int size = heightSize + getPaddingTop() + getPaddingBottom() + (mLines.size() - 1) * mLineSpace + mActionBarTopHeight + mActionBarBottomHeight;
        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int top;
        int left;
        int offsetTop;

        Line lastSelectedLine = findLastSelectedLine();
        Line firstSelectedLine = findFirstSelectedLine();

        for (int i = 0; i < mLines.size(); i++) {
            Line line = mLines.get(i);
            List<Item> items = line.getItems();
            left = getPaddingLeft() + mActionBar.getContentPadding();

            if (firstSelectedLine != null && firstSelectedLine.index > line.index) {
                offsetTop = -mActionBarTopHeight;
            } else if (lastSelectedLine != null && lastSelectedLine.index < line.index) {
                offsetTop = mActionBarBottomHeight;
            } else {
                offsetTop = 0;
            }

            for (int j = 0; j < items.size(); j++) {
                Item item = items.get(j);
                top = getPaddingTop() + i * (item.height + mLineSpace) + offsetTop + mActionBarTopHeight;
                View child = item.view;
                int oldTop = child.getTop();
                child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
                if (oldTop != top) {
                    int translationY = oldTop - top;
                    child.setTranslationY(translationY);
                    child.animate().translationYBy(-translationY).setDuration(200).start();
                }
                left += child.getMeasuredWidth() + mItemSpace;
            }
        }

        if (firstSelectedLine != null && lastSelectedLine != null) {
            mActionBar.setVisibility(View.VISIBLE);
            mActionBar.setAlpha(1);
            int oldTop = mActionBar.getTop();
            int actionBarTop = firstSelectedLine.index * (firstSelectedLine.getHeight() + mLineSpace) + getPaddingTop();
            mActionBar.layout(getPaddingLeft(), actionBarTop, getPaddingLeft() + mActionBar.getMeasuredWidth(), actionBarTop + mActionBar.getMeasuredHeight());
            if (oldTop != actionBarTop) {
                int translationY = oldTop - actionBarTop;
                mActionBar.setTranslationY(translationY);
                mActionBar.animate().translationYBy(-translationY).setDuration(200).start();
            }
        } else {
            if (mActionBar.getVisibility() == View.VISIBLE){
                mActionBar.animate().alpha(0).setDuration(200).setListener(mActionBarAnimationListener).start();
            }
        }
    }

还有上面的几个按钮在BigBangActionBar.java

 private void initSubViews() {
        Context context = getContext();

        mBorder = ContextCompat.getDrawable(context, R.drawable.bigbang_action_bar_bg);
        mBorder.setCallback(this);

        mSearch = new ImageView(context);
        mSearch.setImageResource(R.mipmap.bigbang_action_search);
        mSearch.setOnClickListener(this);
        mShare = new ImageView(context);
        mShare.setImageResource(R.mipmap.bigbang_action_share);
        mShare.setOnClickListener(this);
        mCopy = new ImageView(context);
        mCopy.setImageResource(R.mipmap.bigbang_action_copy);
        mCopy.setOnClickListener(this);

        addView(mSearch, createLayoutParams());
        addView(mShare, createLayoutParams());
        addView(mCopy, createLayoutParams());

        setWillNotDraw(false);

        mActionGap = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15, getResources().getDisplayMetrics());
        mContentPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics());
    }
 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int childCount = getChildCount();
        int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            child.measure(measureSpec, measureSpec);
        }

        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(width, height + mContentPadding + mSearch.getMeasuredHeight());
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();

        layoutSubView(mSearch, mActionGap, 0);
        layoutSubView(mShare, width - mActionGap * 2 - mShare.getMeasuredWidth() - mCopy.getMeasuredWidth(), 0);
        layoutSubView(mCopy, width - mActionGap - mCopy.getMeasuredWidth(), 0);

        Rect oldBounds = mBorder.getBounds();
        Rect newBounds = new Rect(0, mSearch.getMeasuredHeight() / 2, width, height);

        if (!oldBounds.equals(newBounds)) {
            ObjectAnimator.ofObject(mBorder, "bounds", new RectEvaluator(), oldBounds, newBounds).setDuration(200).start();
        }
    }

至此大家可以看看实现。一般来说剪切板需要保存历史记录,下面开始实现数据库。喜欢使用原生数据库的实现,下节可以不用看了,在本文中数据库使用GreenDao实现数据库操作。
相信大家肯定接触过,下面只是简单的介绍一下。
3. 添加数据库等使用技术
在main下面建立java-gen(大家都这样写,其实无所谓),在build.gradle中设置

 sourceSets{
        main{
            java.srcDirs=['src/main/java','src/main/java-gen']
        }
    }

还有一个 compile ‘de.greenrobot:greendao:1.3.7’,将greendao的包导进去。
接下来开始建立依赖库,在依赖库的build.gradle写:

apply plugin: 'java'

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'de.greenrobot:greendao-generator:1.3.1'
}

sourceCompatibility = "1.8"
targetCompatibility = "1.8"

在java文件中写

 public static void main(String[] args) throws Exception {
        // 正如你所见的,你创建了一个用于添加实体(Entity)的模式(Schema)对象。
        // 两个参数分别代表:数据库版本号与自动生成代码的包路径。
        Schema schema = new Schema(1, "com.longshihan.sqlite");
        //      当然,如果你愿意,你也可以分别指定生成的 Bean 与 DAO 类所在的目录,只要如下所示:
        //      Schema schema = new Schema(1, "me.itangqi.bean");
        //      schema.setDefaultJavaPackageDao("me.itangqi.dao");

        // 模式(Schema)同时也拥有两个默认的 flags,分别用来标示 entity 是否是 activie 以及是否使用 keep sections。
        // schema2.enableActiveEntitiesByDefault();
        // schema2.enableKeepSectionsByDefault();

        // 一旦你拥有了一个 Schema 对象后,你便可以使用它添加实体(Entities)了。
        addNote(schema);

        // 最后我们将使用 DAOGenerator 类的 generateAll() 方法自动生成代码,此处你需要根据自己的情况更改输出目录(既之前创建的 java-gen)。
        // 其实,输出目录的路径可以在 build.gradle 中设置,有兴趣的朋友可以自行搜索,这里就不再详解。
        new DaoGenerator().generateAll(schema, "D:\\1\\BigBang1\\app\\src\\main\\java-gen");
    }

    /**
     * @param schema
     */
    private static void addNote(Schema schema) {
        // 一个实体(类)就关联到数据库中的一张表,此处表名为「Note」(既类名)
        Entity note = schema.addEntity("Clip");
        // 你也可以重新给表命名
        // note.setTableName("NODE");

        // greenDAO 会自动根据实体类的属性值来创建表字段,并赋予默认值
        // 接下来你便可以设置表中的字段:
        note.addIdProperty();
        note.addStringProperty("text").notNull();
        // 与在 Java 中使用驼峰命名法不同,默认数据库中的命名是使用大写和下划线来分割单词的。
        // For example, a property called “creationDate” will become a database column “CREATION_DATE”.
        note.addStringProperty("comment");
        note.addDateProperty("date");
    }

这个全凭自己的需求。建立表等,这种方法比较简单,但适合扩展,或者使用注解的方式生成。
运行或在java-gen中生成表名的类Clip,ClipDao,DaoMaster和DaoSession这三个。
新建一个DaoManager,来管理ClipDao。或者在Application 中实现也可。
所以在服务中就可以添加信息。

 String comment = primaryClip.getDescription().toString();
                            Clip note = new Clip(null, item.getText().toString(), comment, new Date());
                            App.getInstance().App_session().getClipDao().insert(note);

在MainActivity 可以使用RecyclerView 列表。将数据库中所有数据取出,显示在界面中。

效果图:
这里写图片描述

以上这些就是全部内容,附上我的github地址:链接

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值