基于Andirod+SQLite实现的记账本APP

基于andirod实现的账本APP

1.项目简介

小凯账本APP是为了解决用户快捷方便记账的一款APP,所有的核心功能都是围绕记账展开,目的是为了简化方便记账流程,所以该APP的基本功能需求如表所示。

1.1需求分析

|----------|-------------------------------------------------------------------------------------------------------------------------------------------|
| 功能 | 说明 |
| 添加账单分类 | 账单分为收入和支出两类,具体在向下细分为用户自定义账单的分类,例如工资、红包等收入和购物、饮食等开销支出。用户自定义的账单分类需要用户根据自己的具体需求来添加和更改,方便用户在记账的过程当中能够更加清晰快捷的定位自己的账单归类。 |
| 账单的增删查改 | 账单的增删查改是该APP的核心要素,在开发这一块需要对于用户的操作体验流程做一个详细的规划,让用户可以用最简单,操作次数较少,体验过程较为流程的情况之下对自己账单进行操作管理。当然在数据库这一方面也要优化,保证APP的性能和流程。 |
| 账单数据报表分析 | 用户除了可以看到自己的账单明细之外,该APP还需要提供一个报表给用户提供直观的数据分析,通过总结和分析用户近日的消费支出数据给用户以图表反馈,体现该APP的应用价值。 |
| 语音备忘功能 | 相对于其他的记账APP,小凯账本还提供以语音备忘功能,该功能的应用场景在于用户在支出收入匆忙的环境下,没有充足的时间去记下自己的账单,通过语音备忘功能,用户只需在短短几秒的时间类通过单手的简单操作就可以将自己需要记录的账单以语音的方式保存下来,方便日后再去处理。 |
| 云端备份功能 | 以上所有的功能都是不需要联网的单机环境下就可以完成的,使用的是安卓自带的SQLite本地数据库来存储数据的,但是用户总会有数据迁移的功能需求,将用户的数据备份到云端服务器上可以保证数据永不丢失,并且可以实现跨设备去同步自己账本,这可以增加用户的使用粘度,提升用户的使用体验。 |

1.2框架

1.3.页面UI

2.项目实现

2.1页面布局

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.tonkia">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.INTERNET" />


    <application
        android:name="org.litepal.LitePalApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait"
            android:windowSoftInputMode="adjustPan|stateHidden">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".AddActivity"
            android:screenOrientation="portrait" />
        <activity android:name=".UpdateActivity"></activity>
    </application>

</manifest>

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimaryDark"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:layout_marginTop="20dp"
            android:layout_marginRight="10dp"
            android:orientation="horizontal">

            <ImageButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/ic_keyboard_arrow_left_black_24dp"
                android:onClick="back" />

            <TextView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:gravity="center"
                android:text="记一笔"
                android:textColor="@color/colorAccent" />

            <ImageButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@drawable/ic_playlist_add_black_24dp"
                android:onClick="addItem" />
        </LinearLayout>


        <android.support.design.widget.TabLayout
            android:id="@+id/tab"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="30dp"
            android:layout_marginRight="30dp"
            app:tabIndicatorColor="@color/colorAccent"
            app:tabSelectedTextColor="@color/colorAccent"
            app:tabTextColor="@color/centerBtnClick " />

    </LinearLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></android.support.v4.view.ViewPager>

</LinearLayout>

2.2工具类

public class AudioPlayUtils {


    private boolean isPlaying;
    private ExecutorService mExecutorService;
    private MediaPlayer mediaPlayer;
    private AudioPlayerListener myListener;
    private Handler mHandler = new Handler();

    public interface AudioPlayerListener {
        public void onError();

        public void onComplete();

        public void currentPosition(int position);

        public void onStart(int total);
    }



    public void setAudioPlayerListener(AudioPlayerListener myListener) {
        this.myListener = myListener;
    }

    public AudioPlayUtils() {
        mExecutorService = Executors.newSingleThreadExecutor();
        isPlaying = false;
    }

    public void playAudio(final File mFile) {
        if (null != mFile && !isPlaying && myListener != null) {
            isPlaying = true;
            mExecutorService.submit(new Runnable() {
                @Override
                public void run() {
                    startPlay(mFile);
                }
            });
        }
    }


    private void startPlay(File mFile) {
        try {
            //初始化播放器
            mediaPlayer = new MediaPlayer();
            //设置播放音频数据文件
            mediaPlayer.setDataSource(mFile.getAbsolutePath());
            //设置播放监听事件
            mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mediaPlayer) {
                    //播放完成
                    playEndOrFail(true);
                }
            });
            //播放发生错误监听事件
            mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
                @Override
                public boolean onError(MediaPlayer mediaPlayer, int i, int i1) {
                    playEndOrFail(false);
                    return true;
                }
            });
            //播放器音量配置
            mediaPlayer.setVolume(1, 1);
            //是否循环播放
            mediaPlayer.setLooping(false);
            //准备及播放
            mediaPlayer.prepare();
            mediaPlayer.start();
            int total = mediaPlayer.getDuration();
            myListener.onStart(total);
            updatePositon();
        } catch (IOException e) {
            e.printStackTrace();
            //播放失败正理
            playEndOrFail(false);
        }

    }

    private int SPACE = 100;
    private Runnable update = new Runnable() {
        @Override
        public void run() {
            updatePositon();
        }
    };

    private void updatePositon() {
        if (mediaPlayer != null) {
            int positon = mediaPlayer.getCurrentPosition();
            myListener.currentPosition(positon);
            mHandler.postDelayed(update, SPACE);
        }
    }

录音工具类

public class AudioPlayUtils {


    private boolean isPlaying;
    private ExecutorService mExecutorService;
    private MediaPlayer mediaPlayer;
    private AudioPlayerListener myListener;
    private Handler mHandler = new Handler();

    public interface AudioPlayerListener {
        public void onError();

        public void onComplete();

        public void currentPosition(int position);

        public void onStart(int total);
    }



    public void setAudioPlayerListener(AudioPlayerListener myListener) {
        this.myListener = myListener;
    }

    public AudioPlayUtils() {
        mExecutorService = Executors.newSingleThreadExecutor();
        isPlaying = false;
    }

    public void playAudio(final File mFile) {
        if (null != mFile && !isPlaying && myListener != null) {
            isPlaying = true;
            mExecutorService.submit(new Runnable() {
                @Override
                public void run() {
                    startPlay(mFile);
                }
            });
        }
    }


    private void startPlay(File mFile) {
        try {
            //初始化播放器
            mediaPlayer = new MediaPlayer();
            //设置播放音频数据文件
            mediaPlayer.setDataSource(mFile.getAbsolutePath());
            //设置播放监听事件
            mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mediaPlayer) {
                    //播放完成
                    playEndOrFail(true);
                }
            });
            //播放发生错误监听事件
            mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
                @Override
                public boolean onError(MediaPlayer mediaPlayer, int i, int i1) {
                    playEndOrFail(false);
                    return true;
                }
            });
            //播放器音量配置
            mediaPlayer.setVolume(1, 1);
            //是否循环播放
            mediaPlayer.setLooping(false);
            //准备及播放
            mediaPlayer.prepare();
            mediaPlayer.start();
            int total = mediaPlayer.getDuration();
            myListener.onStart(total);
            updatePositon();
        } catch (IOException e) {
            e.printStackTrace();
            //播放失败正理
            playEndOrFail(false);
        }

    }

    private int SPACE = 100;
    private Runnable update = new Runnable() {
        @Override
        public void run() {
            updatePositon();
        }
    };

    private void updatePositon() {
        if (mediaPlayer != null) {
            int positon = mediaPlayer.getCurrentPosition();
            myListener.currentPosition(positon);
            mHandler.postDelayed(update, SPACE);
        }
    }

http请求工具类

public class HttpUtils {
    private static final String url = "http://192.168.43.208/Checkout";

    //获取验证码
    public static void requestIdentifyCode(String number, Callback callback) {
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .readTimeout(20, TimeUnit.SECONDS)
                .build();

        //post方式提交的数据
        FormBody formBody = new FormBody.Builder()
                .add("type", "requestCode")
                .add("phone", number)
                .build();

        final Request request = new Request.Builder()
                .url(url + "/UserServlet")//请求的url
                .post(formBody)
                .build();

        Call call = okHttpClient.newCall(request);
        //加入队列 异步操作
        call.enqueue(callback);
    }

    //验证码登录
    public static void loginWithCode(String number, String code, Callback callback) {
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .readTimeout(20, TimeUnit.SECONDS)
                .build();
        //post方式提交的数据
        FormBody formBody = new FormBody.Builder()
                .add("type", "loginCode")
                .add("phone", number)
                .add("code", code)
                .build();
        final Request request = new Request.Builder()
                .url(url + "/UserServlet")//请求的url
                .post(formBody)
                .build();
        Call call = okHttpClient.newCall(request);
        //加入队列 异步操作
        call.enqueue(callback);
    }

    //自动登录
    public static void login(String number, String secretCode, Callback callback) {
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .readTimeout(20, TimeUnit.SECONDS)
                .build();
        //post方式提交的数据
        FormBody formBody = new FormBody.Builder()
                .add("type", "login")
                .add("phone", number)
                .add("secretCode", secretCode)
                .build();

        final Request request = new Request.Builder()
                .url(url + "/UserServlet")//请求的url
                .post(formBody)
                .build();

        Call call = okHttpClient.newCall(request);
        //加入队列 异步操作
        call.enqueue(callback);
    }
}

2.3返回上一级回调方法

public class DealFragment extends Fragment {

    private SimpleDateFormat sfd = new SimpleDateFormat("yyyy-MM-dd   hh:mm:ss");
    private List<AudioInfo> mList;
    private RecyclerView rv;
    private MyAdapt adapt;
    private AudioPlayUtils audioPlayUtils;
    private RoundCornerProgressBar progressBar;
    private int total;
    private TextView tvEmpty;

    @Override

    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_deal, container, false);
        rv = view.findViewById(R.id.recycle);
        tvEmpty = view.findViewById(R.id.tv_empty);
        adapt = new MyAdapt();
        rv.setAdapter(adapt);
        rv.setLayoutManager(new LinearLayoutManager(getContext()));
        audioPlayUtils = new AudioPlayUtils();
        audioPlayUtils.setAudioPlayerListener(new MyAudioPlayerListenr());
        List<AudioInfo> initList = LitePal.findAll(AudioInfo.class);
        freshList(initList);
        return view;
    }

    //更新列表
    public void freshList(List<AudioInfo> initList) {
        Collections.reverse(initList);
        this.mList = initList;
        adapt.notifyDataSetChanged();
        if (mList.size() > 0) {
            tvEmpty.setVisibility(View.GONE);
        } else {
            tvEmpty.setVisibility(View.VISIBLE);
        }
    }

    //播放回调方法
    private class MyAudioPlayerListenr implements AudioPlayUtils.AudioPlayerListener {
        @Override
        public void onError() {
            getActivity().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(getContext(), "播放失败!", Toast.LENGTH_SHORT).show();
                    progressBar.setProgress(0);
                    progressBar = null;
                    total = 0;
                }
            });

        }

        @Override
        public void onComplete() {
            getActivity().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    progressBar.setProgress(0);
                    progressBar = null;
                    total = 0;
                }
            });

        }

        @Override
        public void currentPosition(final int position) {
            getActivity().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    progressBar.setProgress(100 * position / total);
                }
            });
        }

        @Override
        public void onStart(int total) {
            DealFragment.this.total = total;
        }
    }


    class MyAdapt extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
        @NonNull
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
            View view = LayoutInflater.from(getContext()).inflate(R.layout.item_audio, viewGroup, false);

            return new MyViewHolder(view);
        }

        @Override
        public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder viewHolder, int i) {
            final AudioInfo ai = mList.get(i);
            ((MyViewHolder) viewHolder).tvName.setText(sfd.format(new Date(ai.getTime())));
            ((MyViewHolder) viewHolder).rpb.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (DealFragment.this.progressBar == null) {
                        DealFragment.this.progressBar = ((MyViewHolder) viewHolder).rpb;
                        audioPlayUtils.playAudio(new File(ai.getPath()));
                    }
                }
            });
            ((MyViewHolder) viewHolder).btnDel.setVisibility(View.GONE);
            ((MyViewHolder) viewHolder).rpb.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View view) {
                    ((MyViewHolder) viewHolder).tvName.setText("删除该条语音          ");
                    ((MyViewHolder) viewHolder).btnDel.setVisibility(View.VISIBLE);

                    ((MyViewHolder) viewHolder).rpb.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            ((MyViewHolder) viewHolder).tvName.setText(sfd.format(new Date(ai.getTime())));
                            ((MyViewHolder) viewHolder).rpb.setOnClickListener(new View.OnClickListener() {
                                @Override
                                public void onClick(View view) {
                                    if (DealFragment.this.progressBar == null) {
                                        DealFragment.this.progressBar = ((MyViewHolder) viewHolder).rpb;
                                        audioPlayUtils.playAudio(new File(ai.getPath()));
                                    }
                                }
                            });
                            ((MyViewHolder) viewHolder).btnDel.setVisibility(View.GONE);
                        }
                    });
                    ((MyViewHolder) viewHolder).btnDel.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            File f = new File(ai.getPath());
                            f.delete();
                            ai.delete();
                            List<AudioInfo> initList = LitePal.findAll(AudioInfo.class);
                            freshList(initList);
                            //修改主页的badge
                            ((MainActivity) getActivity()).setBadge(mList.size());
                        }
                    });

                    return true;
                }
            });
        }

        @Override
        public int getItemCount() {
            return mList.size();
        }

        class MyViewHolder extends RecyclerView.ViewHolder {

            public TextView tvName;
            public RoundCornerProgressBar rpb;
            public ImageButton btnDel;

            public MyViewHolder(@NonNull View itemView) {
                super(itemView);
                tvName = itemView.findViewById(R.id.tv_name);
                rpb = itemView.findViewById(R.id.progressBar);
                btnDel = itemView.findViewById(R.id.btn_del);
            }
        }
    }


}

2.4点击事件类

public class AddActivity extends AppCompatActivity {
    private TabLayout tabLayout;
    private ViewPager viewPager;
    private MyViewPagerAdapter myViewPagerAdapter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_add);
        tabLayout = findViewById(R.id.tab);
        viewPager = findViewById(R.id.viewpager);
        myViewPagerAdapter = new MyViewPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(myViewPagerAdapter);
        //设置viewpager和tablayout联动
        tabLayout.setupWithViewPager(viewPager);
    }

    private class MyViewPagerAdapter extends FragmentPagerAdapter {
        private final String[] title = new String[]{"支出", "收入"};
        private Fragment[] fragments = new Fragment[]{new OutputFragment(), new
                InputFragment()};

        public MyViewPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int i) {
            return fragments[i];
        }

        @Override
        public int getCount() {
            return title.length;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return title[position];
        }
    }

    public void back(View view) {
        finish();
    }

    //点击添加item
    public void addItem(View view) {
        final BottomDialog bd = BottomDialog.create(getSupportFragmentManager());
        bd.setViewListener(new BottomDialog.ViewListener() {
            @Override
            public void bindView(View v) {
                final int type = tabLayout.getSelectedTabPosition();
                TextView tv = v.findViewById(R.id.title);
                final EditText et = v.findViewById(R.id.name);
                StateButton btnOk = v.findViewById(R.id.btn_ok);
                StateButton btnCancel = v.findViewById(R.id.btn_cancel);
                String title = type == 0 ? "添加支出项" : "添加收入项";
                tv.setText(title);
                //添加
                btnOk.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        String name = et.getText().toString().trim();
                        if (!TextUtils.isEmpty(name)) {
                            DealItem di = new DealItem();
                            di.setType(type == 0 ? DealItem.OUTPUT : DealItem.INPUT);
                            di.setItemName(name);
                            di.save();
                            notifyChildFragment(type);
                            bd.dismiss();
                        }
                    }
                });
                //退出
                btnCancel.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        bd.dismiss();
                    }
                });
            }
        }).setLayoutRes(R.layout.dialog_add_item).setDimAmount(0.5f).setCancelOutside(true).setTag("AddItemDialog");
        bd.show();
    }

    private void notifyChildFragment(int type) {
        if (type == 0) {
            OutputFragment outputFragment = (OutputFragment) myViewPagerAdapter.getItem(0);
            outputFragment.initList();
        } else {
            InputFragment inputFragment = (InputFragment) myViewPagerAdapter.getItem(1);
            inputFragment.initList();
        }
    }

}

2.5主启动类

public class MainActivity extends AppCompatActivity {
    //Fragment
    private FragmentManager fragmentManager;
    private Fragment from;
    private DealFragment dealFragment;
    private DetailFragment detailFragment;
    private SelfFragment selfFragment;
    private TableFragment tableFragment;

    //录音类
    private AudioRecoderUtils mAudiorecoder;

    //监听滑动
    private float mPosY;
    private float mCurPosY;
    private float moveDis = 300;

    //控件
    private FloatingActionButton centerBtn;
    private ImageView bindBadge;
    private BadgeView badgeView;
    private SpaceNavigationView spaceNavigationView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //处理Fragment的切换
        fragmentManager = getSupportFragmentManager();
        //初始打开的Fragment
        from = fragmentManager.findFragmentById(R.id.fragment_content);
        detailFragment = (DetailFragment) from;
        //init bottomNavigation
        spaceNavigationView = findViewById(R.id.space);
        spaceNavigationView.initWithSaveInstanceState(savedInstanceState);
        spaceNavigationView.addSpaceItem(new SpaceItem(getString(R.string.navigation_detail), R.drawable.ic_event_note_black_24dp));
        spaceNavigationView.addSpaceItem(new SpaceItem(getString(R.string.navigation_table), R.drawable.ic_call_missed_outgoing_black_24dp));
        spaceNavigationView.addSpaceItem(new SpaceItem(getString(R.string.navigation_deal), R.drawable.ic_access_time_black_24dp));
        spaceNavigationView.addSpaceItem(new SpaceItem(getString(R.string.navigation_self), R.drawable.ic_person_black_24dp));
        //设置点击/长按监听
        spaceNavigationView.setSpaceOnClickListener(new MySpaceOnClickListener());
        spaceNavigationView.setSpaceOnLongClickListener(new MySpaceOnLongClickListener());
        //设置中间button的颜色
        spaceNavigationView.setCentreButtonRippleColor(ContextCompat.getColor(this, R.color.centerBtnClick));
        //只显示ICON
        spaceNavigationView.showIconOnly();

        //录音类初始化
        mAudiorecoder = new AudioRecoderUtils();
        mAudiorecoder.setOnAudioStatusUpdateListener(new MyAudioListener());

        //需要先初始化deal的badge
        bindBadge = findViewById(R.id.badge);
        badgeView = BadgeFactory.create(this)
                .setTextColor(ContextCompat.getColor(this, R.color.colorAccent))
                .setWidthAndHeight(18, 18)
                .setBadgeBackground(ContextCompat.getColor(this, R.color.colorPrimary))
                .setTextSize(8)
                .setBadgeGravity(Gravity.RIGHT | Gravity.TOP)
                .setBadgeCount(0)
                .setShape(BadgeView.SHAPE_CIRCLE)
                .bind(bindBadge);

        freshDeal();
    }


    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        spaceNavigationView.onSaveInstanceState(outState);
    }

    private class MySpaceOnClickListener implements SpaceOnClickListener {
        @Override
        public void onCentreButtonClick() {
            Intent i = new Intent(MainActivity.this, AddActivity.class);
            startActivityForResult(i, 0);
        }

        @Override
        public void onItemClick(int itemIndex, String itemName) {
            FragmentTransaction transaction = fragmentManager.beginTransaction();
            switch (itemIndex) {
                case 0:
                    replaceFragment(detailFragment, transaction);
                    break;
                case 1:
                    if (tableFragment == null) {
                        tableFragment = new TableFragment();
                        transaction.add(R.id.mainContent, tableFragment);
                    }
                    replaceFragment(tableFragment, transaction);
                    break;
                case 2:
                    if (dealFragment == null) {
                        dealFragment = new DealFragment();
                        transaction.add(R.id.mainContent, dealFragment);
                    }
                    replaceFragment(dealFragment, transaction);
                    break;
                case 3:
                    if (selfFragment == null) {
                        selfFragment = new SelfFragment();
                        transaction.add(R.id.mainContent, selfFragment);
                    }
                    replaceFragment(selfFragment, transaction);
                    break;
            }
        }

        @Override
        public void onItemReselected(int itemIndex, String itemName) {

        }
    }

    //切换Fragment
    private void replaceFragment(Fragment to, FragmentTransaction transaction) {
        transaction.hide(from).show(to).commit();
        from = to;
    }

    private class MySpaceOnLongClickListener implements SpaceOnLongClickListener {
        @Override
        public void onCentreButtonLongClick() {
            //-----------------------长按开始录音---------------------------
            if (centerBtn == null) {
                //反射找到中间Button
                try {
                    Field field = SpaceNavigationView.class.getDeclaredField("centreButton");
                    field.setAccessible(true);
                    centerBtn = (FloatingActionButton) field.get(spaceNavigationView);
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            if (Build.VERSION.SDK_INT > 22) {
                //先检查权限
                if (ContextCompat.checkSelfPermission(MainActivity.this,
                        Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(MainActivity.this,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(MainActivity.this,
                            new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE},
                            0);
                } else {
                    //开始录音菜监听
                    startAudioRecord();
                }
            } else {
                //开始录音菜监听
                startAudioRecord();
            }
        }

        @Override
        public void onItemLongClick(int itemIndex, String itemName) {

        }
    }

    //录音的界面
    private TextView tvTime;
    private TextView tvTip;
    private VoiceLineView voiceView;
    BottomDialog bdAudio;

    private void startAudioRecord() {
        bdAudio = BottomDialog.create(getSupportFragmentManager());
        bdAudio.setViewListener(new BottomDialog.ViewListener() {
            @Override
            public void bindView(View v) {
                tvTime = v.findViewById(R.id.tv_time);
                tvTip = v.findViewById(R.id.tv_tip);
                voiceView = v.findViewById(R.id.voicLine);
                //先初始化控件再监听
                centerBtn.setOnTouchListener(new MyTouchListener());
            }
        }).setLayoutRes(R.layout.dialog_record).setDimAmount(0.5f).setCancelOutside(false).setTag("AudioDialog").show();
        //everything is ok 后再录音
        mAudiorecoder.startRecord();
    }

    //录音MoveTouch监听
    class MyTouchListener implements View.OnTouchListener {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            switch (motionEvent.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mPosY = motionEvent.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    mCurPosY = motionEvent.getY();

                    if (mCurPosY - mPosY < 0
                            && (Math.abs(mCurPosY - mPosY) > moveDis)) {
                        tvTip.setText("手指松开取消");
                    } else {
                        tvTip.setText("手指上滑取消");
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    mCurPosY = motionEvent.getY();
                    if (mCurPosY - mPosY < 0
                            && (Math.abs(mCurPosY - mPosY) > moveDis)) {
                        Toast.makeText(MainActivity.this, "语音备注取消", Toast.LENGTH_SHORT).show();
                        mAudiorecoder.cancelRecord();
                    } else {
                        mAudiorecoder.stopRecord();
                    }
                    centerBtn.setOnTouchListener(null);
                    bdAudio.dismiss();
                    break;
            }
            return true;
        }
    }

    //AudioRecorder的回调
    class MyAudioListener implements AudioRecoderUtils.OnAudioStatusUpdateListener {
        @Override
        public void onUpdate(double db, long time) {
            tvTime.setText(TimeUtils.recordTime(time));
            voiceView.setVolume((int) db - 40);
        }

        @Override
        public void onStop(String filePath) {
            AudioInfo ai = new AudioInfo();
            ai.setPath(filePath);
            long time = System.currentTimeMillis();
            ai.setTime(time);
            ai.save();
            freshDeal();
        }
    }

    private void freshDeal() {
        List<AudioInfo> initList = LitePal.findAll(AudioInfo.class);
        //录音保存文件成功后要更新badge和DealFragment页面
        if (dealFragment != null) {
            dealFragment.freshList(initList);
        }
        int count = initList.size();
        setBadge(count);
    }

    //dealFragment onResume中调用
    public void setBadge(int count) {
        if (count > 0) {
            badgeView.setVisibility(View.VISIBLE);
            badgeView.setBadgeCount(count);
        } else {
            badgeView.setVisibility(View.INVISIBLE);
        }
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        //添加record后返回
        if (resultCode == 1) {
            //切换到detail页面
            FragmentTransaction transaction = fragmentManager.beginTransaction();
            replaceFragment(detailFragment, transaction);
            //定位到当前时间
            Calendar calendar = Calendar.getInstance();
            int year = calendar.get(Calendar.YEAR);
            int month = calendar.get(Calendar.MONTH);
            detailFragment.setDate(year, month);
            //反射大法好  厉害厉害  哈哈哈哈哈哈哈哈  Space选择第一个
            try {
                Method method = SpaceNavigationView.class.getDeclaredMethod("updateSpaceItems", int.class);
                method.setAccessible(true);
                method.invoke(spaceNavigationView, 0);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

//    public void onRequestPermissionsResult(int requestCode,
//                                           @NonNull String[] permissions, @NonNull int[] grantResults) {
//
//        if (requestCode == 0) {
//            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//                //授权成功
//            }
//            return;
//        }
//        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//    }
}

3.功能演示

3.1明细页
3.2录音功能

4.三方引用

  • bumptech/glide
  • AlexLiuSheng/BadgeView
  • akexorcist/Android-RoundCornerProgressBar
  • ws123/VoiceLine
  • lecho/hellocharts-android
  • shaohui10086/BottomDialog
  • armcha/Space-Navigation-View
  • LitePalFramework/LitePal
  • square/okhttp
  • hdodenhof/CircleImageView
  • niniloveyou/StateButton
相关推荐
加酶洗衣粉4 小时前
MongoDB部署模式
数据库·mongodb
Suyuoa4 小时前
mongoDB常见指令
数据库·mongodb
添砖,加瓦4 小时前
MongoDB详细讲解
数据库·mongodb
Zda天天爱打卡4 小时前
【趣学SQL】第二章:高级查询技巧 2.2 子查询的高级用法——SQL世界的“俄罗斯套娃“艺术
数据库·sql
我的运维人生4 小时前
MongoDB深度解析与实践案例
数据库·mongodb·运维开发·技术共享
步、步、为营5 小时前
解锁.NET配置魔法:打造强大的配置体系结构
数据库·oracle·.net
张3蜂5 小时前
docker Ubuntu实战
数据库·ubuntu·docker
苏-言6 小时前
MyBatis最佳实践:动态 SQL
数据库·sql·mybatis
doubt。7 小时前
【BUUCTF】[RCTF2015]EasySQL1
网络·数据库·笔记·mysql·安全·web安全